Objectives

  • Define missing data and patterns of missingness
  • Identify traditional approaches to missing data
  • Define imputation and multiple imputation
  • Summarize maximum-likelihood estimation for MAR data
  • Define Bayesian multiple imputation
  • Demonstrate how to conduct inference on MI datasets
library(tidyverse)
library(broom)
library(forcats)
library(modelr)
library(stringr)
library(car)
library(rcfss)
library(RColorBrewer)

options(digits = 3)
set.seed(1234)
theme_set(theme_minimal())

Missing data

Causes of missingness

  • Surveys
    • Global or unit non-response - individuals refuse to participate in or answer questions in a survey
    • Item non-response - individual may not know the answer to or refuses to answer a specific question on the survey
  • Errors in data collection
  • Intentionally built into the research design (e.g. survey experiments)
  • Censored values
    • Data values in the study are censored
    • Survival analysis aka duration analysis aka event-history analysis
    • Follow individuals for a fixed period of time waiting for an event to happen
    • When the event occurs, record the time elapsed
    • If the event never occurs, the outcome is censored (i.e. missing)

Patterns of missingness

Missing completely at random (MCAR)

Data are missing completely at random if the missing data can be regarded as a simple random sample of the complete data. The probability that a data value is missing is unrelated to the data value itself or any other value, missing or observed, in the data set.

Missing at random (MAR)

Data are missing at random if the missingness is related to the observed data but not the missing data. That is, conditional on the observed data, missingness is as if random. Consider a survey where certain individuals refuse to report their income, and these people differ systematically in income from the sample as a whole.1 However, if the observations are independently sampled so that one respondent’s decision to withhold information about income is independent of other respondents’ decision to withhold information about income, and if conditional on the information that the respondent does provide (e.g. education, occupation, political affiliation) failure to provide information on income is independent of income itself, then the data is MAR.

MCAR is a special case of MAR.

Missing not at random (MNAR)

If missingness is related to the missing values themselves even when the information in the observed data is taken into account, then the missing data is missing not at random. So if conditional on all the observed data, individuals with higher incomes are more likely to withhold information about their incomes, then the missing income data is MNAR.

Why we should care about missingness patterns

If data are MCAR or MAR, then we don’t need to model the process that generates the missing data in order to accommodate the missing data. This means that when data are MCAR or MAR, the mechanism that produces the missing data is ignorable. But when data are MNAR, the mechanism is non-ignorable and it becomes necessary to model this mechanism in order to deal with the missingness in a valid way.

Even more depressingly, you rarely if ever can test to see if your data are MCAR, MAR, or MNAR because the information needed to make that determination is missing.

Simulated examples of missingness patterns

n_sim <- 250 # Number of random samples

# Target parameters for univariate normal distributions
rho <- 2 / 3

mu1 <- 10
mu2 <- 20

s1 <- 9
s2 <- 16
s1s2 <- sqrt(s1) * sqrt(s2) * rho

# Parameters for bivariate normal distribution
mu <- c(mu1, mu2) # Mean 
sigma <- matrix(c(s1, s1s2, s1s2, s2), 2) # Covariance matrix
data_sim <- MASS::mvrnorm(n_sim, mu, sigma) %>%
  as_tibble %>%
  rename(x1 = V1,
         x2 = V2)
# correlation coefficient
cor(data_sim)
##       x1    x2
## x1 1.000 0.649
## x2 0.649 1.000
# regression models
lm(x2 ~ x1, data = data_sim)
## 
## Call:
## lm(formula = x2 ~ x1, data = data_sim)
## 
## Coefficients:
## (Intercept)           x1  
##      11.484        0.853
lm(x1 ~ x2, data = data_sim)
## 
## Call:
## lm(formula = x1 ~ x2, data = data_sim)
## 
## Coefficients:
## (Intercept)           x2  
##      0.0948       0.4943
# plot of data
ggplot(data_sim, aes(x1, x2)) +
  geom_point() +
  geom_smooth(method = "lm") +
  labs(title = "Complete data",
       x = expression(X[1]),
       y = expression(X[2]))

What happens to the data under the three mechanisms for generating missing data?

mcar <- data_sim %>%
  mutate(na = ifelse(row_number(x2) %in% sample(seq_len(n_sim), 100), TRUE, FALSE))

ggplot(mcar, aes(x1, x2)) +
  geom_point(aes(alpha = na)) +
  geom_smooth(data = filter(mcar, !na),
              aes(color = "Non-missing values"),
              method = "lm", se = FALSE, fullrange = TRUE) +
  geom_smooth(aes(color = "All values"),
              method = "lm", se = FALSE, fullrange = TRUE) +
  scale_color_brewer(palette = "Dark2") +
  scale_alpha_manual(values = c(.3, 1)) +
  labs(title = "Missing completely at random",
       x = expression(X[1]),
       y = expression(X[2]),
       color = "Regression line",
       alpha = "Missing")

100 observations on \(X_2\) are selected at random and set to missing. Here the missing values of \(X_2\) are MCAR and the subset of valid observations is a simple random sample of the full data set. The regression line with and without the missing values is relatively similar, though slightly different due to the lower sample size needed to calculate the parameter estimates and the standard errors.

mar <- data_sim %>%
  mutate(na = .5 + (2 / 3) * (x1 - 10) + rnorm(n_sim, sd = 2),
         na = logit2prob(na),
         na = as.logical(round(na)))

ggplot(mar, aes(x1, x2)) +
  geom_point(aes(alpha = na)) +
  geom_smooth(data = filter(mar, !na),
              aes(color = "Non-missing values"),
              method = "lm", se = FALSE, fullrange = TRUE) +
  geom_smooth(aes(color = "All values"),
              method = "lm", se = FALSE, fullrange = TRUE) +
  scale_color_brewer(palette = "Dark2") +
  scale_alpha_manual(values = c(.3, 1)) +
  labs(title = "Missing at random",
       x = expression(X[1]),
       y = expression(X[2]),
       color = "Regression line",
       alpha = "Missing")

Here an observation’s missingness on \(X_2\) is related to its observed value of \(X_1\) in the logistic regression functional form:

\[\Pr(X_{i2} \text{is missing}) = \frac{1}{1 + \exp[\frac{1}{2} + \frac{2}{3}(X_{i1} - 10)]}\]

As \(X_1\) increases, the probability that \(X_2\) is missing increases. In the resulting dataset, 141 observations are missing. Because \(X_1\) and \(X_2\) are positively correlated, there are relatively fewer small values of \(X_2\) in the observed data versus the complete data. If we only look at observations with valid data on both \(X_1\) and \(X_2\), then this subset of observations also has relatively few small values of \(X_1\). But because \(X_1\) is fully observed, the missing data on \(X_2\) are MAR.

mnar <- data_sim %>%
  mutate(na = .5 + (1 / 2) * (x2 - 20) + rnorm(n_sim, sd = 2),
         na = logit2prob(na),
         na = as.logical(round(na)))

ggplot(mnar, aes(x1, x2)) +
  geom_point(aes(alpha = na)) +
  geom_smooth(data = filter(mnar, !na),
              aes(color = "Non-missing values"),
              method = "lm", se = FALSE, fullrange = TRUE) +
  geom_smooth(aes(color = "All values"),
              method = "lm", se = FALSE, fullrange = TRUE) +
  scale_color_brewer(palette = "Dark2") +
  scale_alpha_manual(values = c(.3, 1)) +
  labs(title = "Missing not at random",
       x = expression(X[1]),
       y = expression(X[2]),
       color = "Regression line",
       alpha = "Missing")

Finally, here an observation’s missingness on \(X_2\) is related to the (potentially) unobserved value of \(X_2\) itself:

\[\Pr(X_{i2} \text{is missing}) = \frac{1}{1 + \exp[\frac{1}{2} + \frac{1}{2}(X_{i2} - 20)]}\]

As \(X_2\) increases, the probability that \(X_2\) is missing increases. In the resulting dataset, 141 observations are missing. Here too there are relatively few small values of \(X_2\). Because missingness on \(X_2\) depends on the value of \(X_2\), the missing data are MNAR. But again, we only know this because we generated the missingness ourselves; in the real world, you rarely can verify this pattern of missingness.

Traditional approaches to missing data

In deciding how to handle missingness, we should consider three questions:

  1. Does the method provide consistent estimates of the population parameters?
  2. Does the method provide valid statistical inferences?
  3. Does the method use the observed data efficiently or does it recklessly discard information?

Discarding data

Complete-case analysis

Complete-case analysis (or listwise or casewise deletion) is probably the most common approach for handling missing data. In this method, you ignore any observations with missing values on variables necessary to estimate the model.

The advantages of this method are that it:

  • Is simple
  • Provides consistent estimates and valid inferences when the data is missing completely at random
  • Provides consistent estimates of regression coefficients and valid inferences when missingness on all the variables in a regression does not depend on the response variable (even if the data is not MCAR)

The disadvantages of this method are that it:

  • Discards valuable information, decreasing efficiency
  • Becomes less efficient as missingness occurs in multiple variables. Even if missingness is only 5% for each individual variable, for a dataset with 10 variables we would expect only \(100 \times .95^{10} = 60%\) of the observations to be usable
  • When data is MAR or MNAR, listwise deletion provides biased results and invalid inferences

Available-case analysis

Available-case analysis (or pairwise deletion) uses all non missing observations to compute each statistic of interest. In OLS, this means estimating the regression coefficients from the means, variances, and covariances of the variables rather than directly from the observations. While this appears to use more information than complete-case analysis, it can sometimes be less efficient. And by basing each statistic of interest on different subsets of the data, results can become nonsensical (e.g. correlations outside of the \([-1, +1]\) range). Finally, this method is much more difficult to implement outside of OLS to other GLMs.

Imputation

Imputation refers to filling in missing data with plausible imputed values. The completed data set is then analyzed using traditional methods.

Unconditional mean imputation

Unconditional mean imputation replaces the missing value with the arithmetic mean of the observed values for the variable in question. Doing so preserves the mean of the variable, but decreases its variance and its covariance with other variables. This can lead to biased regression coefficients and invalid inferences even if the data is MCAR.

Conditional-mean imputation

Conditional-mean imputation replaces missing data with predicted values obtained from a statistical learning model, typically a regression model. Using the available data, regress each variable with missing data on the other variables in the data set. Then use the regression model to generate predicted values for the missing data in the regressed variable. However this still leaves two problems:

  1. Imputed values still tend to be less variable than the real data because they lack residual variation
  2. We still fail to account for uncertainty in the estimates of the regression coefficients used to obtain the imputed values

How do all of these methods stack up?

get_miss_stat <- function(df){
  df %>%
    summarize(mu_1 = mean(x1, na.rm = TRUE),
              mu_2 = mean(x2, na.rm = TRUE),
              sigma_1 = var(x1, use = "complete.obs"),
              sigma_2 = var(x2, use = "complete.obs"),
              sigma_12 = cov(., use = "complete.obs")[1, 2],
              rho = cor(., use = "complete.obs")[1, 2],
              beta_12 = lm(x2 ~ x1, data = .) %>% coef(.) %>% .[[2]],
              beta_21 = lm(x1 ~ x2, data = .) %>% coef(.)%>% .[[2]]
    )
}

data_miss <- list(
  mcar = data_sim %>%
    mutate(x2 = replace(x2, sample(seq_len(n_sim), 100), NA)),
  mar = data_sim %>%
    mutate(na = .5 + (2 / 3) * (x1 - 10) + rnorm(n_sim, sd = 2),
           na = logit2prob(na),
           na = as.logical(round(na)),
           x2 = replace(x2, na, NA)) %>%
    select(-na),
  mnar = data_sim %>%
    mutate(na = .5 + (1 / 2) * (x2 - 20) + rnorm(n_sim, sd = 2),
           na = logit2prob(na),
           na = as.logical(round(na)),
           x2 = replace(x2, na, NA)) %>%
    select(-na)
)

# complete cases
complete_cases <- data_miss %>%
  map(na.omit) %>%
  map_df(get_miss_stat, .id = "id")

# available cases
available_cases <- data_miss %>%
  map_df(~ .x %>%
    summarize(mu_1 = mean(x1, na.rm = TRUE),
              mu_2 = mean(x2, na.rm = TRUE),
              sigma_1 = var(x1, use = "complete.obs"),
              sigma_2 = var(x2, use = "complete.obs"),
              sigma_12 = cov(., use = "pairwise.complete.obs")[1, 2],
              rho = cor(., use = "pairwise.complete.obs")[1, 2],
              beta_12 = psych::mat.regress("x2", "x1",
                   data = cov(., use = "pairwise.complete.obs"),
                   n.obs = nrow(na.omit(.))) %>%
                .$beta %>%
                .[[1]],
              beta_21 = psych::mat.regress("x1", "x2",
                   data = cov(., use = "pairwise.complete.obs"),
                   n.obs = nrow(na.omit(.))) %>%
                .$beta %>%
                .[[1]]
    ), .id = "id")
# mean imputation
mean_imp <- data_miss %>%
  map(~ mutate(.x, x2 = ifelse(is.na(x2), mean(x2, na.rm = TRUE), x2))) %>%
  map_df(get_miss_stat, .id = "id")

# regression imputation
reg_imp <- data_miss %>%
  map(~ .x %>%
        mutate(x2_imp = lm(x2 ~ x1, data = .) %>%
                 predict(., newdata = .x),
               x2 = ifelse(is.na(x2), x2_imp, x2))) %>%
  map_df(get_miss_stat, .id = "id")

sum_stats <- bind_rows(
  `Population parameters` = data_frame(
    mu_1 = 10,
    mu_2 = 20,
    sigma_1 = 9,
    sigma_2 = 16,
    sigma_12 = 8,
    rho = .667,
    beta_12 = .5,
    beta_21 = .889),
  `Complete data` = get_miss_stat(data_sim),
  `Complete cases` = complete_cases,
  `Available cases` = available_cases,
  `Mean imputation` = mean_imp,
  `Regression imputation` = reg_imp,
  .id = "method"
) %>%
  mutate(id = ifelse(method == "Population parameters", "Parameter", id),
         id = ifelse(method == "Complete data", "Complete data", id)) %>%
  gather(param, value, -method, -id) %>%
  mutate(param = ifelse(param == "mu_1", "mu[1]", param),
         param = ifelse(param == "mu_2", "mu[2]", param),
         param = ifelse(param == "sigma_1", "sigma[1]^2", param),
         param = ifelse(param == "sigma_2", "sigma[2]^2", param),
         param = ifelse(param == "sigma_12", "sigma[12]", param),
         param = ifelse(param == "beta_12", "beta[12]", param),
         param = ifelse(param == "beta_21", "beta[21]", param))
sum_stats %>%
  filter(id %in% c("mcar", "mar", "mnar")) %>%
  mutate(id = factor(id, levels = c("mcar", "mar", "mnar"),
                     labels = c("MCAR", "MAR", "MNAR"))) %>%
  ggplot(aes(id, value, color = method, shape = method)) +
  facet_wrap( ~ param, nrow = 2, scales = "free_y",
              labeller = "label_parsed") +
  geom_point() +
  geom_hline(data = filter(sum_stats, id == "Parameter"),
             aes(yintercept = value)) +
  scale_color_brewer(type = "qual", palette = "Dark2", 
                     guide = guide_legend(nrow = 2)) +
  labs(x = NULL,
       y = "Estimated parameter value",
       color = NULL,
       shape = NULL) +
  theme(axis.text.x = element_text(angle = 30),
        legend.position = "bottom")

Imputation estimation strategies

Maximum-likelihood estimation for data MAR

When data are MAR (or MCAR), we can use maximum-likelihood estimation to estimate the parameters of interest and generate imputed values for the missing data. This requires several assumptions about the missingness mechanism and the distribution of the complete data.

Let \(p(\mathbf{X}, \theta) = p(\mathbf{X}_{\text{obs}}, \mathbf{X}_{\text{mis}}; \theta)\) represent the joint probability density for the complete data \(\mathbf{X}\), which is composed of the observed and missing components denoted by \(\mathbf{X}_{\text{obs}}, \mathbf{X}_{\text{mis}}\). The vector \(\theta\) contains the unknown parameters on which the complete-data distribution depends. For example, if the variables in \(\mathbf{X}\) are multivariate normally distributed, then \(\theta\) includes the population means and covariances among the variables.

If data is MAR, then the ML estimate \(\hat{\theta}\) of \(\theta\) can be obtained from the marginal distribution of the observed data by integrating over the missing data:

\[p(\mathbf{X}_\text{obs}; \theta) = \int{p(\mathbf{X}_{\text{obs}}, \mathbf{X}_{\text{mis}}; \theta)} d\mathbf{X}_{\text{mis}}\]

We’ll skip the math for all of this2, but the important thing to note is that the ML estimate only has a closed-form solution when missingness follows an arbitrary pattern (i.e. MAR). We can use iterative processes such as an expectation-maximization (EM) algorithm to find the ML estimates in the absence of arbitrary patterns of missingness. Typically software will use an expectation-maximization (EM) algorithm to find the ML estimates in the absence of arbitrary patterns of missingness. When the parameter estimates stop changing from one iteration to the next, they converge to the ML estimates \(\hat{\theta}\).

  • Implemented in Amelia for R

Predictive mean matching

Combines regression model with matching procedure.

  1. For cases with no missing data, estimate a linear regression of \(x\) on \(z\), producing a set of coefficients \(b\).
  2. Make a random draw from the posterior predictive distribution of \(b\), producing a new set of coefficients \(b*\). Typically this would be a random draw from a multivariate normal distribution with mean \(b\) and the estimated covariance matrix of \(b\) (with an additional random draw for the residual variance). This step is necessary to produce sufficient variability in the imputed values, and is common to all robust methods for multiple imputation.
  3. Using \(b*\), generate predicted values for \(x\) for all cases, both those with data missing on \(x\) and those with data present.
  4. For each case with missing \(x\), identify a set of cases with observed \(x\) whose predicted values are close to the predicted value for the case with missing data.
  5. From among those close cases, randomly choose one and assign its observed value to substitute for the missing value.

Step 2 distinguishes this from pure conditional-mean imputation by accounting for additional uncertainty in the model(s). By default in most software, \(k=5\) is the number of matches observations from which to draw the imputed value.

  • Implemented in mice and mi for R

Random forest model

Alternatively, we can use a random forest algorithm to pursue a non-parametric imputation strategy. In this approach, you build a random forest model for each variable to predict its value using the other variables in the dataset. These results are used to generate imputed values for all the missing variables and observations.

  • Implemented in missForest for R

Deep learning

Because everyone is doing it. No stable packages implement off-the-shelf methods, but researchers are developing deep learning methods for imputing missing values.

Generating multiple imputations

Bayesian multiple imputation (MI) is a flexible method for dealing with missing data MAR. It starts by specifying the distribution of the complete data; typically the data is assumed to be multivariate normal. The key difference is that this method reflects uncertainty associated with missing data by imputing multiple values for each missing data value (i.e. multiple imputation), producing several complete datasets. Each dataset is then analyzed independently and in parallel, estimating parameters of interest and standard errors for each imputed dataset. The estimated parameters are then averaged together across the imputed datasets. Standard errors are also combined, taking into account the variation among the estimates in the several datasets and capturing the added uncertainty due to having to deal with missing data.

The method is Bayesian because each estimate of the parameters and standard errors is drawn from the posterior distribution of the parameters, typically assuming a non-informative (flat) prior distribution. The important thing to note is that this method directly accounts for our uncertainty associated with both the sampling variance of the coefficients used in the imputation model as well as the uncertainty derived from the missingness itself.

Inference for individual coefficients

We use this method to produce \(g\) complete datasets. MI estimates of population parameters of interest (such as a regression coefficient) are obtained by averaging over the imputed datasets:

\[\tilde{\beta}_j \equiv \frac{\sum_{l=1}^g B_j^{(l)}}{g}\]

This averaging method applies to any type of parameter that for the separate estimates is approximately normally distributed. This applies to OLS regression estimates, GLM coefficient estimates, or by any parametric method of regression analysis.

Standard errors of the estimated coefficients are obtained by combining information about within- and between-imputation variation in the coefficients:

\[\tilde{\text{SE}}(\tilde{\beta}_j) = \sqrt{V_j^{(W)} + \frac{g + 1}{g} V_j^{(B)}}\]

where the within-imputation component is:

\[V_j^{(W)} = \frac{\sum_{l=1}^g \text{SE}^2(B_j^{(l)})}{g}\]

and the between-imputation component is:

\[V_j^{(B)} = \frac{\sum_{l=1}^g (B_j^{(l)} - \tilde{B}_j)^2}{g-1}\]

\(\text{SE}^2(B_j^{(l)})\) is the standard error of \(B_j\), computed in the usual manner for the \(l\)th imputed dataset.

Inference based on \(\tilde{\text{SE}}(\tilde{\beta}_j)\) and \(\tilde{\text{SE}}(\tilde{\beta}_j)\) follows the \(t\)-distribution with degrees of freedom:

\[df_j = (g-1) \left ( 1 + \frac{g}{g+1} \times \frac{V_j^{(W)}}{V_j^{(B)}} \right)^2\]

Practical considerations for multiple imputation

The MI method is typically implemented assuming the complete data follows a multivariate normal distribution. Violation of this assumption isn’t necessarily a deal-breaker for relying on MI estimates. However MI can only preserve features of the dataset represented in the imputation model. Therefore you need to think carefully about which features (variables) need to be preserved when building the imputation model to ensure those particular features will appear in the final statistical model.

  • Include variables in the imputation model that make the assumption of ignorable missingness reasonable. Remember that the MI method assumes data is MAR. So for this to work on data MNAR, we want to build a predictive model that does a great job of predicting missing values. Typically this includes using variables that will be in the final statistical model as well as variables in the dataset not used in the final statistical model, variables strongly correlated with the variable with missingness (as measured by the complete observations), and even the response variable itself. Think of the imputation model as a pure prediction model - you are not conducting inference on the imputation model itself, so it can be highly complex.
  • Transform variables to approximately normal. After the imputed data are obtained, you can transform them back to their original scales prior to analyzing the completed datasets.
  • Adjust the imputed data to resemble the original data. So if you have a dichotomous variable with imputed values of \(.3\) or \(.785\), round them to \(0\) and \(1\).
  • Make sure the imputation model captures relevant features of the data. Again, consider how the data will eventually be analyzed. The multivariate normal distribution ensures that regressions of one variable on others are linear and additive. If you are estimating a nonlinear relationship (either polynomial or interactive), then provide for that in the imputation model (explicitly add the polynomial or interaction term).
  • \(g\) doesn’t need to be large. For most situations, \(g=5\) or \(g=10\) is actually suitable for statistical inference.

Regression model of infant mortality with MI

un <- read_delim("data/UnitedNations.txt", delim = " ")
ggplot(un, aes(GDPperCapita, infantMortality)) +
  geom_point() +
  geom_smooth(se = FALSE) +
  scale_x_continuous(labels = scales::dollar) +
  labs(x = "GDP per capita (in USD)",
       y = "Infant mortality rate (per 1,000)")

The above figure shows the relationship between GDP per capita and infant mortality in 193 countries, part of a larger dataset of 207 countries compiled by the United Nations. The amount of missingness in the figure is therefore small, approximately 7% of the cases.

Let’s now estimate a linear regression model of infant mortality not only on GDP per capita but also the percentage of married women practicing contraception and the average number of years of education for women. To linearize the model, we log-transform both infant mortality and GDP.

ggplot(un, aes(GDPperCapita, infantMortality)) +
  geom_point() +
  geom_smooth(method = "lm", se = FALSE) +
  scale_x_log10(labels = scales::dollar) +
  scale_y_log10() +
  labs(x = "GDP per capita (in USD)",
       y = "Infant mortality rate (per 1,000)")

mortal_mod <- lm(log(infantMortality) ~ log(GDPperCapita) +
                   contraception + educationFemale,
                 data = un)
tidy(mortal_mod)
##                term estimate std.error statistic  p.value
## 1       (Intercept)   6.8840   0.29039     23.71 1.58e-31
## 2 log(GDPperCapita)  -0.2943   0.05765     -5.10 3.85e-06
## 3     contraception  -0.0113   0.00424     -2.66 1.01e-02
## 4   educationFemale  -0.0770   0.03378     -2.28 2.63e-02

With listwise deletion, we are left with just 62 observations. The missingness for each variable is:

un %>%
  select(infantMortality, GDPperCapita, contraception, educationFemale) %>%
  summarize_all(funs(sum(is.na(.)))) %>%
  knitr::kable()
infantMortality GDPperCapita contraception educationFemale
6 10 63 131

Amelia

Amelia is a package for R that provides a Bayesian EM-based algorithm for multiple imputation.3 To create multiple imputations in Amelia, we use amelia():

library(Amelia)
un.out <- amelia(as.data.frame(un), m = 5, idvars = c("country", "region"))
## Warning: There are observations in the data that are completely missing. 
##          These observations will remain unimputed in the final datasets. 
## -- Imputation 1 --
## 
##   1  2  3  4  5  6  7  8  9 10 11 12 13 14 15 16 17 18 19 20
##  21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40
##  41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56
## 
## -- Imputation 2 --
## 
##   1  2  3  4  5  6  7  8  9 10 11 12 13 14 15 16 17 18 19 20
##  21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40
##  41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60
##  61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80
##  81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98
## 
## -- Imputation 3 --
## 
##   1  2  3  4  5  6  7  8  9 10 11 12 13 14 15 16 17 18 19 20
##  21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40
##  41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57
## 
## -- Imputation 4 --
## 
##   1  2  3  4  5  6  7  8  9 10 11 12 13 14 15 16 17 18 19 20
##  21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40
##  41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60
##  61 62 63 64 65 66
## 
## -- Imputation 5 --
## 
##   1  2  3  4  5  6  7  8  9 10 11 12 13 14 15 16 17 18 19 20
##  21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40
##  41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60
##  61 62 63 64 65 66 67 68 69 70

If your data frame is a tibble, you need to turn back into a plain data frame using as.data.frame() in order to successfully impute the data.

Here we specify country and region are id variables (text strings) and we don’t want to use them to generate imputed values. By default, amelia() uses all the variables in their raw forms to impute missing values for each variable. Clearly we still want to tune this approach, but for now let’s run with it. The list of imputed data frames is stored in the imputations element:

glimpse(un.out$imputations)
## List of 5
##  $ imp1:'data.frame':    207 obs. of  14 variables:
##   ..$ country               : chr [1:207] "Afghanistan" "Albania" "Algeria" "American.Samoa" ...
##   ..$ region                : chr [1:207] "Asia" "Europe" "Africa" "Asia" ...
##   ..$ tfr                   : num [1:207] 6.9 2.6 3.81 1.86 NA ...
##   ..$ contraception         : num [1:207] -12 71.9 52 43.3 NA ...
##   ..$ educationMale         : num [1:207] 4.05 11.02 11.1 13 NA ...
##   ..$ educationFemale       : num [1:207] 0.319 10.646 9.9 12.313 NA ...
##   ..$ lifeMale              : num [1:207] 45 68 67.5 68 NA ...
##   ..$ lifeFemale            : num [1:207] 46 74 70.3 73 NA ...
##   ..$ infantMortality       : num [1:207] 154 32 44 11 NA 124 24 22 25 6 ...
##   ..$ GDPperCapita          : num [1:207] 2848 863 1531 3207 NA ...
##   ..$ economicActivityMale  : num [1:207] 87.5 78.3 76.4 58.8 NA ...
##   ..$ economicActivityFemale: num [1:207] 7.2 68.9 7.8 42.4 NA ...
##   ..$ illiteracyMale        : num [1:207] 52.8 8.941 26.1 0.264 NA ...
##   ..$ illiteracyFemale      : num [1:207] 85 16.02 51 0.36 NA ...
##   ..- attr(*, "spec")=List of 2
##   .. ..$ cols   :List of 14
##   .. ..$ default: list()
##   .. .. ..- attr(*, "class")= chr [1:2] "collector_guess" "collector"
##   .. ..- attr(*, "class")= chr "col_spec"
##  $ imp2:'data.frame':    207 obs. of  14 variables:
##   ..$ country               : chr [1:207] "Afghanistan" "Albania" "Algeria" "American.Samoa" ...
##   ..$ region                : chr [1:207] "Asia" "Europe" "Africa" "Asia" ...
##   ..$ tfr                   : num [1:207] 6.9 2.6 3.81 3.39 NA ...
##   ..$ contraception         : num [1:207] 17.3 50.8 52 19.8 NA ...
##   ..$ educationMale         : num [1:207] 7.2 7.72 11.1 12.77 NA ...
##   ..$ educationFemale       : num [1:207] 2.11 9.37 9.9 12.76 NA ...
##   ..$ lifeMale              : num [1:207] 45 68 67.5 68 NA ...
##   ..$ lifeFemale            : num [1:207] 46 74 70.3 73 NA ...
##   ..$ infantMortality       : num [1:207] 154 32 44 11 NA 124 24 22 25 6 ...
##   ..$ GDPperCapita          : num [1:207] 2848 863 1531 8316 NA ...
##   ..$ economicActivityMale  : num [1:207] 87.5 90.1 76.4 58.8 NA ...
##   ..$ economicActivityFemale: num [1:207] 7.2 60.4 7.8 42.4 NA ...
##   ..$ illiteracyMale        : num [1:207] 52.8 2.955 26.1 0.264 NA ...
##   ..$ illiteracyFemale      : num [1:207] 85 3.52 51 0.36 NA ...
##   ..- attr(*, "spec")=List of 2
##   .. ..$ cols   :List of 14
##   .. ..$ default: list()
##   .. .. ..- attr(*, "class")= chr [1:2] "collector_guess" "collector"
##   .. ..- attr(*, "class")= chr "col_spec"
##  $ imp3:'data.frame':    207 obs. of  14 variables:
##   ..$ country               : chr [1:207] "Afghanistan" "Albania" "Algeria" "American.Samoa" ...
##   ..$ region                : chr [1:207] "Asia" "Europe" "Africa" "Asia" ...
##   ..$ tfr                   : num [1:207] 6.9 2.6 3.81 3.35 NA ...
##   ..$ contraception         : num [1:207] -7.24 56.17 52 67.65 NA ...
##   ..$ educationMale         : num [1:207] 6.16 9.37 11.1 14 NA ...
##   ..$ educationFemale       : num [1:207] 3.58 10.19 9.9 13.66 NA ...
##   ..$ lifeMale              : num [1:207] 45 68 67.5 68 NA ...
##   ..$ lifeFemale            : num [1:207] 46 74 70.3 73 NA ...
##   ..$ infantMortality       : num [1:207] 154 32 44 11 NA 124 24 22 25 6 ...
##   ..$ GDPperCapita          : num [1:207] 2848 863 1531 3568 NA ...
##   ..$ economicActivityMale  : num [1:207] 87.5 78.9 76.4 58.8 NA ...
##   ..$ economicActivityFemale: num [1:207] 7.2 63 7.8 42.4 NA ...
##   ..$ illiteracyMale        : num [1:207] 52.8 1.728 26.1 0.264 NA ...
##   ..$ illiteracyFemale      : num [1:207] 85 14.92 51 0.36 NA ...
##   ..- attr(*, "spec")=List of 2
##   .. ..$ cols   :List of 14
##   .. ..$ default: list()
##   .. .. ..- attr(*, "class")= chr [1:2] "collector_guess" "collector"
##   .. ..- attr(*, "class")= chr "col_spec"
##  $ imp4:'data.frame':    207 obs. of  14 variables:
##   ..$ country               : chr [1:207] "Afghanistan" "Albania" "Algeria" "American.Samoa" ...
##   ..$ region                : chr [1:207] "Asia" "Europe" "Africa" "Asia" ...
##   ..$ tfr                   : num [1:207] 6.9 2.6 3.81 1.66 NA ...
##   ..$ contraception         : num [1:207] 19.9 35.3 52 66.9 NA ...
##   ..$ educationMale         : num [1:207] 6.52 5.18 11.1 13.19 NA ...
##   ..$ educationFemale       : num [1:207] 2.28 5.72 9.9 13.49 NA ...
##   ..$ lifeMale              : num [1:207] 45 68 67.5 68 NA ...
##   ..$ lifeFemale            : num [1:207] 46 74 70.3 73 NA ...
##   ..$ infantMortality       : num [1:207] 154 32 44 11 NA 124 24 22 25 6 ...
##   ..$ GDPperCapita          : num [1:207] 2848 863 1531 4049 NA ...
##   ..$ economicActivityMale  : num [1:207] 87.5 89.5 76.4 58.8 NA ...
##   ..$ economicActivityFemale: num [1:207] 7.2 34 7.8 42.4 NA ...
##   ..$ illiteracyMale        : num [1:207] 52.8 16.589 26.1 0.264 NA ...
##   ..$ illiteracyFemale      : num [1:207] 85 19.43 51 0.36 NA ...
##   ..- attr(*, "spec")=List of 2
##   .. ..$ cols   :List of 14
##   .. ..$ default: list()
##   .. .. ..- attr(*, "class")= chr [1:2] "collector_guess" "collector"
##   .. ..- attr(*, "class")= chr "col_spec"
##  $ imp5:'data.frame':    207 obs. of  14 variables:
##   ..$ country               : chr [1:207] "Afghanistan" "Albania" "Algeria" "American.Samoa" ...
##   ..$ region                : chr [1:207] "Asia" "Europe" "Africa" "Asia" ...
##   ..$ tfr                   : num [1:207] 6.9 2.6 3.81 2.18 NA ...
##   ..$ contraception         : num [1:207] 20.6 32.5 52 71.5 NA ...
##   ..$ educationMale         : num [1:207] 7.37 10.27 11.1 15.1 NA ...
##   ..$ educationFemale       : num [1:207] 4.22 10.08 9.9 16.11 NA ...
##   ..$ lifeMale              : num [1:207] 45 68 67.5 68 NA ...
##   ..$ lifeFemale            : num [1:207] 46 74 70.3 73 NA ...
##   ..$ infantMortality       : num [1:207] 154 32 44 11 NA 124 24 22 25 6 ...
##   ..$ GDPperCapita          : num [1:207] 2848 863 1531 15980 NA ...
##   ..$ economicActivityMale  : num [1:207] 87.5 73.7 76.4 58.8 NA ...
##   ..$ economicActivityFemale: num [1:207] 7.2 33.9 7.8 42.4 NA ...
##   ..$ illiteracyMale        : num [1:207] 52.8 15.825 26.1 0.264 NA ...
##   ..$ illiteracyFemale      : num [1:207] 85 24.52 51 0.36 NA ...
##   ..- attr(*, "spec")=List of 2
##   .. ..$ cols   :List of 14
##   .. ..$ default: list()
##   .. .. ..- attr(*, "class")= chr [1:2] "collector_guess" "collector"
##   .. ..- attr(*, "class")= chr "col_spec"
##  - attr(*, "class")= chr [1:2] "mi" "list"

Each of these imputed datasets is a complete data frame. So for example, we could plot the same scatterplot of GDP vs. infant mortality with the imputed values for the 14 countries with missing values.

un.out$imputations %>%
  map(as.data.frame) %>%
  bind_rows(.id = "impute") %>%
  ggplot(aes(GDPperCapita, infantMortality)) +
  geom_point() +
  geom_smooth(se = FALSE) +
  scale_x_continuous(labels = scales::dollar) +
  facet_grid(impute ~ .) +
  labs(x = "GDP per capita (in USD)",
       y = "Infant mortality rate (per 1,000)")

Notice that for some of the imputed datasets, the imputed values are nonsensical; for instance, you cannot have a negative GDP or infant mortality rate. But again, let’s just run with it.

We can use purrr::map() to estimate the linear model from before on the new imputed datasets and extract the coefficients and standard errors with broom::tidy():

models_imp <- data_frame(data = un.out$imputations) %>%
  mutate(model = map(data, ~ lm(log(infantMortality) ~ log(GDPperCapita) +
                                  contraception + educationFemale,
                                data = .x)),
         coef = map(model, tidy)) %>%
  unnest(coef, .id = "id")
models_imp
## # A tibble: 20 × 6
##       id              term estimate std.error statistic   p.value
##    <chr>             <chr>    <dbl>     <dbl>     <dbl>     <dbl>
##  1  imp1       (Intercept)  6.47310   0.16284     39.75  1.30e-96
##  2  imp1 log(GDPperCapita) -0.20206   0.02988     -6.76  1.47e-10
##  3  imp1     contraception -0.00480   0.00241     -2.00  4.72e-02
##  4  imp1   educationFemale -0.14254   0.01799     -7.92  1.60e-13
##  5  imp2       (Intercept)  6.44744   0.14350     44.93 8.28e-106
##  6  imp2 log(GDPperCapita) -0.20265   0.02722     -7.45  2.91e-12
##  7  imp2     contraception -0.00596   0.00206     -2.90  4.18e-03
##  8  imp2   educationFemale -0.13358   0.01461     -9.14  7.46e-17
##  9  imp3       (Intercept)  6.57374   0.15260     43.08 3.79e-103
## 10  imp3 log(GDPperCapita) -0.20811   0.02774     -7.50  2.00e-12
## 11  imp3     contraception -0.00507   0.00224     -2.26  2.48e-02
## 12  imp3   educationFemale -0.14579   0.01707     -8.54  3.42e-15
## 13  imp4       (Intercept)  6.49875   0.17864     36.38  1.29e-89
## 14  imp4 log(GDPperCapita) -0.21912   0.03250     -6.74  1.68e-10
## 15  imp4     contraception -0.00710   0.00228     -3.11  2.14e-03
## 16  imp4   educationFemale -0.11895   0.01596     -7.45  2.75e-12
## 17  imp5       (Intercept)  6.52708   0.16142     40.44  1.24e-97
## 18  imp5 log(GDPperCapita) -0.21800   0.03058     -7.13  1.84e-11
## 19  imp5     contraception -0.00650   0.00218     -2.98  3.27e-03
## 20  imp5   educationFemale -0.12604   0.01722     -7.32  6.09e-12

To conduct inference, we need to average the estimates of the coefficients and the standard errors. mi.meld() from Amelia does the work for us:

mi.meld.plus <- function(df_tidy){
  # transform data into appropriate matrix shape
  coef.out <- df_tidy %>%
    select(id:estimate) %>%
    spread(term, estimate) %>%
    select(-id)
  
  se.out <- df_tidy %>%
    select(id, term, std.error) %>%
    spread(term, std.error) %>%
    select(-id)
  
  combined.results <- mi.meld(q = coef.out, se = se.out)
  
  data_frame(term = colnames(combined.results$q.mi),
             estimate.mi = combined.results$q.mi[1, ],
             std.error.mi = combined.results$se.mi[1, ])
}

# compare results
tidy(mortal_mod) %>%
  left_join(mi.meld.plus(models_imp)) %>%
  select(-statistic, -p.value)
##                term estimate std.error estimate.mi std.error.mi
## 1       (Intercept)   6.8840   0.29039     6.50402      0.16896
## 2 log(GDPperCapita)  -0.2943   0.05765    -0.20999      0.03097
## 3     contraception  -0.0113   0.00424    -0.00588      0.00247
## 4   educationFemale  -0.0770   0.03378    -0.13338      0.02064

We see some differences in our estimated coefficients and standard errors.

Missingness map

missmap() is a useful function in Amelia that visualizes the missingness in the data:

missmap(un.out)

Transforming variables

Let’s think more carefully about what variables to include in the imputation model and how to specify them. First, which variables are highly correlated with contraception and female education?

GGally::ggpairs(select_if(un, is.numeric))

Variables such as total fertility rate and the illiteracy rate for women are strongly correlated with our missing variables. Let’s now limit our imputation model to just the four variables in the original regression model plus the total fertility rate, expectation of life for women, percentage of women engaged in economic activity outside the home, and the illiteracy rate for women.

un_lite <- un %>%
  select(infantMortality, GDPperCapita, contraception, educationFemale,
         tfr, lifeFemale, economicActivityFemale, illiteracyFemale)

GGally::ggpairs(un_lite)

Several of these variables are clearly not normally distributed; transforming these variables will also help make the dataset more multivariate normal, so we can transform them before imputation. We could manually transform them using mutate(), but amelia() includes options for transforming variables as part of the imputation process. This allows us to retain the original values for the statistical modeling.

un_lite.out <- amelia(un_lite, m = 5,
                      logs = c("infantMortality", "GDPperCapita"),
                      sqrt = c("tfr"))
## Warning: There are observations in the data that are completely missing. 
##          These observations will remain unimputed in the final datasets. 
## -- Imputation 1 --
## 
##   1  2  3  4  5  6  7  8  9 10 11 12 13 14 15 16 17 18 19 20
##  21 22 23 24 25 26 27 28 29 30 31 32
## 
## -- Imputation 2 --
## 
##   1  2  3  4  5  6  7  8  9 10 11 12 13 14 15 16 17 18 19 20
##  21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40
##  41 42 43 44
## 
## -- Imputation 3 --
## 
##   1  2  3  4  5  6  7  8  9 10 11 12 13 14 15 16 17 18 19 20
##  21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40
##  41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60
##  61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80
##  81 82 83 84 85 86 87 88 89 90 91
## 
## -- Imputation 4 --
## 
##   1  2  3  4  5  6  7  8  9 10 11 12 13 14 15 16 17 18 19 20
##  21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40
##  41 42
## 
## -- Imputation 5 --
## 
##   1  2  3  4  5  6  7  8  9 10 11 12 13 14 15 16 17 18 19 20
##  21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40
##  41 42 43 44 45 46 47 48 49 50 51 52 53 54

amelia() also includes support for nominal and ordinal variables and cross-sectional time-series data, as well as pure time series data and accounting for leads and lags. See the help file for more details.

What does the resulting model look like now?

models_trans_imp <- data_frame(data = un_lite.out$imputations) %>%
  mutate(model = map(data, ~ lm(log(infantMortality) ~ log(GDPperCapita) +
                                  contraception + educationFemale,
                                data = .x)),
         coef = map(model, tidy)) %>%
  unnest(coef, .id = "id")
models_trans_imp
## # A tibble: 20 × 6
##       id              term estimate std.error statistic   p.value
##    <chr>             <chr>    <dbl>     <dbl>     <dbl>     <dbl>
##  1  imp1       (Intercept)   6.4507   0.16693     38.64  5.43e-95
##  2  imp1 log(GDPperCapita)  -0.2271   0.03471     -6.54  4.88e-10
##  3  imp1     contraception  -0.0101   0.00239     -4.23  3.56e-05
##  4  imp1   educationFemale  -0.0955   0.02010     -4.75  3.82e-06
##  5  imp2       (Intercept)   6.5306   0.17201     37.97  1.24e-93
##  6  imp2 log(GDPperCapita)  -0.3042   0.03822     -7.96  1.26e-13
##  7  imp2     contraception  -0.0146   0.00248     -5.89  1.56e-08
##  8  imp2   educationFemale  -0.0285   0.02346     -1.21  2.26e-01
##  9  imp3       (Intercept)   6.4457   0.17031     37.85  2.15e-93
## 10  imp3 log(GDPperCapita)  -0.2437   0.03737     -6.52  5.59e-10
## 11  imp3     contraception  -0.0153   0.00225     -6.83  9.83e-11
## 12  imp3   educationFemale  -0.0606   0.02074     -2.92  3.89e-03
## 13  imp4       (Intercept)   6.1839   0.16821     36.76  3.57e-91
## 14  imp4 log(GDPperCapita)  -0.1512   0.03652     -4.14  5.11e-05
## 15  imp4     contraception  -0.0109   0.00221     -4.91  1.90e-06
## 16  imp4   educationFemale  -0.1260   0.01892     -6.66  2.56e-10
## 17  imp5       (Intercept)   6.4678   0.15379     42.06 1.44e-101
## 18  imp5 log(GDPperCapita)  -0.2257   0.02989     -7.55  1.48e-12
## 19  imp5     contraception  -0.0137   0.00206     -6.65  2.73e-10
## 20  imp5   educationFemale  -0.0804   0.01562     -5.15  6.23e-07
# compare results
tidy(mortal_mod) %>%
  left_join(mi.meld.plus(models_trans_imp)) %>%
  select(-statistic, -p.value)
##                term estimate std.error estimate.mi std.error.mi
## 1       (Intercept)   6.8840   0.29039      6.4158      0.22183
## 2 log(GDPperCapita)  -0.2943   0.05765     -0.2304      0.06954
## 3     contraception  -0.0113   0.00424     -0.0129      0.00341
## 4   educationFemale  -0.0770   0.03378     -0.0782      0.04483
# cheating on my confidence intervals for this plot
bind_rows(orig = tidy(mortal_mod),
          full_imp = mi.meld.plus(models_imp) %>%
            rename(estimate = estimate.mi,
                   std.error = std.error.mi),
          trans_imp = mi.meld.plus(models_trans_imp) %>%
            rename(estimate = estimate.mi,
                   std.error = std.error.mi),
          .id = "method") %>%
  mutate(method = factor(method, levels = c("orig", "full_imp", "trans_imp"),
                         labels = c("Listwise deletion", "Full imputation",
                                    "Transformed imputation")),
         term = factor(term, levels = c("(Intercept)", "contraception",
                                        "educationFemale", "log(GDPperCapita)"),
                       labels = c("Intercept", "Contraception", "Female education",
                                  "GDP per capita (log)"))) %>%
  ggplot(aes(fct_rev(term), estimate, color = fct_rev(method),
             ymin = estimate - 1.96 * std.error,
             ymax = estimate + 1.96 * std.error)) +
  geom_hline(yintercept = 0, linetype = 2) +
  geom_pointrange(position = position_dodge(.75)) +
  coord_flip() +
  scale_color_discrete(guide = guide_legend(reverse = TRUE)) +
  labs(title = "Comparing regression results",
       x = NULL,
       y = "Estimated parameter",
       color = NULL) +
  theme(legend.position = "bottom")

bind_rows(orig = tidy(mortal_mod),
          full_imp = mi.meld.plus(models_imp) %>%
            rename(estimate = estimate.mi,
                   std.error = std.error.mi),
          trans_imp = mi.meld.plus(models_trans_imp) %>%
            rename(estimate = estimate.mi,
                   std.error = std.error.mi),
          .id = "method") %>%
  mutate(method = factor(method, levels = c("orig", "full_imp", "trans_imp"),
                         labels = c("Listwise deletion", "Full imputation",
                                    "Transformed imputation")),
         term = factor(term, levels = c("(Intercept)", "contraception",
                                        "educationFemale", "log(GDPperCapita)"),
                       labels = c("Intercept", "Contraception", "Female education",
                                  "GDP per capita (log)"))) %>%
  filter(term != "Intercept") %>%
  ggplot(aes(fct_rev(term), estimate, color = fct_rev(method),
             ymin = estimate - 1.96 * std.error,
             ymax = estimate + 1.96 * std.error)) +
  geom_hline(yintercept = 0, linetype = 2) +
  geom_pointrange(position = position_dodge(.75)) +
  coord_flip() +
  scale_color_discrete(guide = guide_legend(reverse = TRUE)) +
  labs(title = "Comparing regression results",
       subtitle = "Omitting intercept from plot",
       x = NULL,
       y = "Estimated parameter",
       color = NULL) +
  theme(legend.position = "bottom")

Multiple imputation outside of GLMs

  • Tree-based inference - missing value becomes a feature of the data

MI in Python

Acknowledgments

Session Info

devtools::session_info()
##  setting  value                       
##  version  R version 3.5.1 (2018-07-02)
##  system   x86_64, darwin15.6.0        
##  ui       X11                         
##  language (EN)                        
##  collate  en_US.UTF-8                 
##  tz       America/Chicago             
##  date     2019-01-02                  
## 
##  package      * version date       source        
##  abind          1.4-5   2016-07-21 CRAN (R 3.5.0)
##  assertthat     0.2.0   2017-04-11 CRAN (R 3.5.0)
##  backports      1.1.2   2017-12-13 CRAN (R 3.5.0)
##  base         * 3.5.1   2018-07-05 local         
##  base64enc      0.1-3   2015-07-28 CRAN (R 3.5.0)
##  bindr          0.1.1   2018-03-13 CRAN (R 3.5.0)
##  bindrcpp     * 0.2.2   2018-03-29 CRAN (R 3.5.0)
##  broom        * 0.5.0   2018-07-17 CRAN (R 3.5.0)
##  car          * 3.0-0   2018-04-02 CRAN (R 3.5.0)
##  carData      * 3.0-1   2018-03-28 CRAN (R 3.5.0)
##  cellranger     1.1.0   2016-07-27 CRAN (R 3.5.0)
##  cli            1.0.0   2017-11-05 CRAN (R 3.5.0)
##  colorspace     1.3-2   2016-12-14 CRAN (R 3.5.0)
##  compiler       3.5.1   2018-07-05 local         
##  crayon         1.3.4   2017-09-16 CRAN (R 3.5.0)
##  curl           3.2     2018-03-28 CRAN (R 3.5.0)
##  data.table     1.11.4  2018-05-27 CRAN (R 3.5.0)
##  datasets     * 3.5.1   2018-07-05 local         
##  devtools       1.13.6  2018-06-27 CRAN (R 3.5.0)
##  digest         0.6.18  2018-10-10 cran (@0.6.18)
##  dplyr        * 0.7.8   2018-11-10 cran (@0.7.8) 
##  evaluate       0.11    2018-07-17 CRAN (R 3.5.0)
##  forcats      * 0.3.0   2018-02-19 CRAN (R 3.5.0)
##  foreign        0.8-71  2018-07-20 CRAN (R 3.5.0)
##  ggplot2      * 3.1.0   2018-10-25 cran (@3.1.0) 
##  glue           1.3.0   2018-07-17 CRAN (R 3.5.0)
##  graphics     * 3.5.1   2018-07-05 local         
##  grDevices    * 3.5.1   2018-07-05 local         
##  grid           3.5.1   2018-07-05 local         
##  gtable         0.2.0   2016-02-26 CRAN (R 3.5.0)
##  haven          1.1.2   2018-06-27 CRAN (R 3.5.0)
##  hms            0.4.2   2018-03-10 CRAN (R 3.5.0)
##  htmltools      0.3.6   2017-04-28 CRAN (R 3.5.0)
##  httr           1.3.1   2017-08-20 CRAN (R 3.5.0)
##  jsonlite       1.5     2017-06-01 CRAN (R 3.5.0)
##  knitr          1.20    2018-02-20 CRAN (R 3.5.0)
##  lattice        0.20-35 2017-03-25 CRAN (R 3.5.1)
##  lazyeval       0.2.1   2017-10-29 CRAN (R 3.5.0)
##  lubridate      1.7.4   2018-04-11 CRAN (R 3.5.0)
##  magrittr       1.5     2014-11-22 CRAN (R 3.5.0)
##  memoise        1.1.0   2017-04-21 CRAN (R 3.5.0)
##  methods      * 3.5.1   2018-07-05 local         
##  modelr       * 0.1.2   2018-05-11 CRAN (R 3.5.0)
##  munsell        0.5.0   2018-06-12 CRAN (R 3.5.0)
##  nlme           3.1-137 2018-04-07 CRAN (R 3.5.1)
##  openxlsx       4.1.0   2018-05-26 CRAN (R 3.5.0)
##  pillar         1.3.0   2018-07-14 CRAN (R 3.5.0)
##  pkgconfig      2.0.2   2018-08-16 CRAN (R 3.5.1)
##  plyr           1.8.4   2016-06-08 CRAN (R 3.5.0)
##  purrr        * 0.2.5   2018-05-29 CRAN (R 3.5.0)
##  R6             2.3.0   2018-10-04 cran (@2.3.0) 
##  rcfss        * 0.1.5   2018-05-30 local         
##  RColorBrewer * 1.1-2   2014-12-07 CRAN (R 3.5.0)
##  Rcpp           1.0.0   2018-11-07 cran (@1.0.0) 
##  readr        * 1.1.1   2017-05-16 CRAN (R 3.5.0)
##  readxl         1.1.0   2018-04-20 CRAN (R 3.5.0)
##  rio            0.5.10  2018-03-29 CRAN (R 3.5.0)
##  rlang          0.3.0.1 2018-10-25 CRAN (R 3.5.0)
##  rmarkdown      1.10    2018-06-11 CRAN (R 3.5.0)
##  rprojroot      1.3-2   2018-01-03 CRAN (R 3.5.0)
##  rstudioapi     0.7     2017-09-07 CRAN (R 3.5.0)
##  rvest          0.3.2   2016-06-17 CRAN (R 3.5.0)
##  scales         1.0.0   2018-08-09 CRAN (R 3.5.0)
##  stats        * 3.5.1   2018-07-05 local         
##  stringi        1.2.4   2018-07-20 CRAN (R 3.5.0)
##  stringr      * 1.3.1   2018-05-10 CRAN (R 3.5.0)
##  tibble       * 1.4.2   2018-01-22 CRAN (R 3.5.0)
##  tidyr        * 0.8.1   2018-05-18 CRAN (R 3.5.0)
##  tidyselect     0.2.5   2018-10-11 cran (@0.2.5) 
##  tidyverse    * 1.2.1   2017-11-14 CRAN (R 3.5.0)
##  tools          3.5.1   2018-07-05 local         
##  utils        * 3.5.1   2018-07-05 local         
##  withr          2.1.2   2018-03-15 CRAN (R 3.5.0)
##  xml2           1.2.0   2018-01-24 CRAN (R 3.5.0)
##  yaml           2.2.0   2018-07-25 CRAN (R 3.5.0)
##  zip            1.0.0   2017-04-25 CRAN (R 3.5.0)

  1. It is common in survey research that wealthier individuals are more likely to refuse to answer questions about income compared to poorer individuals.

  2. See Fox ch 20.3 for the gory details

  3. Technical details of the algorithm’s implementation can be read here.

LS0tCnRpdGxlOiAiTWlzc2luZyBkYXRhIGFuZCBtdWx0aXBsZSBpbXB1dGF0aW9uIgphdXRob3I6ICJNQUNTIDMwMjAwIC0gUGVyc3BlY3RpdmVzIG9uIENvbXB1dGF0aW9uYWwgUmVzZWFyY2giCm91dHB1dDoKICBodG1sX2RvY3VtZW50OgogICAgdG9jOiB0cnVlCiAgICB0b2NfZmxvYXQ6IHRydWUKICAgIGNvZGVfZm9sZGluZzogaGlkZQotLS0KCmBgYHtyIHNldHVwLCBpbmNsdWRlID0gRkFMU0V9CmtuaXRyOjpvcHRzX2NodW5rJHNldChjYWNoZSA9IFRSVUUsCiAgICAgICAgICAgICAgICAgICAgICBtZXNzYWdlID0gRkFMU0UsCiAgICAgICAgICAgICAgICAgICAgICB3YXJuaW5nID0gRkFMU0UpCmBgYAoKIyBPYmplY3RpdmVzCgoqIERlZmluZSBtaXNzaW5nIGRhdGEgYW5kIHBhdHRlcm5zIG9mIG1pc3NpbmduZXNzCiogSWRlbnRpZnkgdHJhZGl0aW9uYWwgYXBwcm9hY2hlcyB0byBtaXNzaW5nIGRhdGEKKiBEZWZpbmUgaW1wdXRhdGlvbiBhbmQgbXVsdGlwbGUgaW1wdXRhdGlvbgoqIFN1bW1hcml6ZSBtYXhpbXVtLWxpa2VsaWhvb2QgZXN0aW1hdGlvbiBmb3IgTUFSIGRhdGEKKiBEZWZpbmUgQmF5ZXNpYW4gbXVsdGlwbGUgaW1wdXRhdGlvbgoqIERlbW9uc3RyYXRlIGhvdyB0byBjb25kdWN0IGluZmVyZW5jZSBvbiBNSSBkYXRhc2V0cwoKYGBge3IgcGFja2FnZXMsIGNhY2hlID0gRkFMU0UsIG1lc3NhZ2UgPSBGQUxTRSwgd2FybmluZyA9IEZBTFNFfQpsaWJyYXJ5KHRpZHl2ZXJzZSkKbGlicmFyeShicm9vbSkKbGlicmFyeShmb3JjYXRzKQpsaWJyYXJ5KG1vZGVscikKbGlicmFyeShzdHJpbmdyKQpsaWJyYXJ5KGNhcikKbGlicmFyeShyY2ZzcykKbGlicmFyeShSQ29sb3JCcmV3ZXIpCgpvcHRpb25zKGRpZ2l0cyA9IDMpCnNldC5zZWVkKDEyMzQpCnRoZW1lX3NldCh0aGVtZV9taW5pbWFsKCkpCmBgYAoKIyBNaXNzaW5nIGRhdGEKCiMjIENhdXNlcyBvZiBtaXNzaW5nbmVzcwoKKiBTdXJ2ZXlzCiAgICAqICoqR2xvYmFsIG9yIHVuaXQgbm9uLXJlc3BvbnNlKiogLSBpbmRpdmlkdWFscyByZWZ1c2UgdG8gcGFydGljaXBhdGUgaW4gb3IgYW5zd2VyIHF1ZXN0aW9ucyBpbiBhIHN1cnZleQogICAgKiAqKkl0ZW0gbm9uLXJlc3BvbnNlKiogLSBpbmRpdmlkdWFsIG1heSBub3Qga25vdyB0aGUgYW5zd2VyIHRvIG9yIHJlZnVzZXMgdG8gYW5zd2VyIGEgc3BlY2lmaWMgcXVlc3Rpb24gb24gdGhlIHN1cnZleQoqIEVycm9ycyBpbiBkYXRhIGNvbGxlY3Rpb24KKiBJbnRlbnRpb25hbGx5IGJ1aWx0IGludG8gdGhlIHJlc2VhcmNoIGRlc2lnbiAoZS5nLiBzdXJ2ZXkgZXhwZXJpbWVudHMpCiogKipDZW5zb3JlZCB2YWx1ZXMqKgogICAgKiBEYXRhIHZhbHVlcyBpbiB0aGUgc3R1ZHkgYXJlIGNlbnNvcmVkCiAgICAqIFN1cnZpdmFsIGFuYWx5c2lzIGFrYSBkdXJhdGlvbiBhbmFseXNpcyBha2EgZXZlbnQtaGlzdG9yeSBhbmFseXNpcwogICAgKiBGb2xsb3cgaW5kaXZpZHVhbHMgZm9yIGEgZml4ZWQgcGVyaW9kIG9mIHRpbWUgd2FpdGluZyBmb3IgYW4gZXZlbnQgdG8gaGFwcGVuCiAgICAqIFdoZW4gdGhlIGV2ZW50IG9jY3VycywgcmVjb3JkIHRoZSB0aW1lIGVsYXBzZWQKICAgICogSWYgdGhlIGV2ZW50IG5ldmVyIG9jY3VycywgdGhlIG91dGNvbWUgaXMgY2Vuc29yZWQgKGkuZS4gbWlzc2luZykKCiMjIFBhdHRlcm5zIG9mIG1pc3NpbmduZXNzCgojIyMgTWlzc2luZyBjb21wbGV0ZWx5IGF0IHJhbmRvbSAoTUNBUikKCkRhdGEgYXJlICoqbWlzc2luZyBjb21wbGV0ZWx5IGF0IHJhbmRvbSoqIGlmIHRoZSBtaXNzaW5nIGRhdGEgY2FuIGJlIHJlZ2FyZGVkIGFzIGEgc2ltcGxlIHJhbmRvbSBzYW1wbGUgb2YgdGhlIGNvbXBsZXRlIGRhdGEuIFRoZSBwcm9iYWJpbGl0eSB0aGF0IGEgZGF0YSB2YWx1ZSBpcyBtaXNzaW5nIGlzIHVucmVsYXRlZCB0byB0aGUgZGF0YSB2YWx1ZSBpdHNlbGYgb3IgYW55IG90aGVyIHZhbHVlLCBtaXNzaW5nIG9yIG9ic2VydmVkLCBpbiB0aGUgZGF0YSBzZXQuCgojIyMgTWlzc2luZyBhdCByYW5kb20gKE1BUikKCkRhdGEgYXJlICoqbWlzc2luZyBhdCByYW5kb20qKiBpZiB0aGUgbWlzc2luZ25lc3MgaXMgcmVsYXRlZCB0byB0aGUgb2JzZXJ2ZWQgZGF0YSAqYnV0IG5vdCB0aGUgbWlzc2luZyBkYXRhKi4gVGhhdCBpcywgY29uZGl0aW9uYWwgb24gdGhlIG9ic2VydmVkIGRhdGEsIG1pc3NpbmduZXNzIGlzIGFzIGlmIHJhbmRvbS4gQ29uc2lkZXIgYSBzdXJ2ZXkgd2hlcmUgY2VydGFpbiBpbmRpdmlkdWFscyByZWZ1c2UgdG8gcmVwb3J0IHRoZWlyIGluY29tZSwgYW5kIHRoZXNlIHBlb3BsZSBkaWZmZXIgc3lzdGVtYXRpY2FsbHkgaW4gaW5jb21lIGZyb20gdGhlIHNhbXBsZSBhcyBhIHdob2xlLl5bSXQgaXMgY29tbW9uIGluIHN1cnZleSByZXNlYXJjaCB0aGF0IHdlYWx0aGllciBpbmRpdmlkdWFscyBhcmUgbW9yZSBsaWtlbHkgdG8gcmVmdXNlIHRvIGFuc3dlciBxdWVzdGlvbnMgYWJvdXQgaW5jb21lIGNvbXBhcmVkIHRvIHBvb3JlciBpbmRpdmlkdWFscy5dIEhvd2V2ZXIsIGlmIHRoZSBvYnNlcnZhdGlvbnMgYXJlIGluZGVwZW5kZW50bHkgc2FtcGxlZCBzbyB0aGF0IG9uZSByZXNwb25kZW50J3MgZGVjaXNpb24gdG8gd2l0aGhvbGQgaW5mb3JtYXRpb24gYWJvdXQgaW5jb21lIGlzIGluZGVwZW5kZW50IG9mIG90aGVyIHJlc3BvbmRlbnRzJyBkZWNpc2lvbiB0byB3aXRoaG9sZCBpbmZvcm1hdGlvbiBhYm91dCBpbmNvbWUsIGFuZCBpZiBjb25kaXRpb25hbCBvbiB0aGUgaW5mb3JtYXRpb24gdGhhdCB0aGUgcmVzcG9uZGVudCBkb2VzIHByb3ZpZGUgKGUuZy4gZWR1Y2F0aW9uLCBvY2N1cGF0aW9uLCBwb2xpdGljYWwgYWZmaWxpYXRpb24pIGZhaWx1cmUgdG8gcHJvdmlkZSBpbmZvcm1hdGlvbiBvbiBpbmNvbWUgaXMgaW5kZXBlbmRlbnQgb2YgaW5jb21lIGl0c2VsZiwgdGhlbiB0aGUgZGF0YSBpcyBNQVIuCgo+IE1DQVIgaXMgYSBzcGVjaWFsIGNhc2Ugb2YgTUFSLgoKIyMjIE1pc3Npbmcgbm90IGF0IHJhbmRvbSAoTU5BUikKCklmIG1pc3NpbmduZXNzIGlzIHJlbGF0ZWQgdG8gdGhlIG1pc3NpbmcgdmFsdWVzIHRoZW1zZWx2ZXMgKmV2ZW4gd2hlbiB0aGUgaW5mb3JtYXRpb24gaW4gdGhlIG9ic2VydmVkIGRhdGEgaXMgdGFrZW4gaW50byBhY2NvdW50KiwgdGhlbiB0aGUgbWlzc2luZyBkYXRhIGlzICoqbWlzc2luZyBub3QgYXQgcmFuZG9tKiouIFNvIGlmIGNvbmRpdGlvbmFsIG9uIGFsbCB0aGUgb2JzZXJ2ZWQgZGF0YSwgaW5kaXZpZHVhbHMgd2l0aCBoaWdoZXIgaW5jb21lcyBhcmUgbW9yZSBsaWtlbHkgdG8gd2l0aGhvbGQgaW5mb3JtYXRpb24gYWJvdXQgdGhlaXIgaW5jb21lcywgdGhlbiB0aGUgbWlzc2luZyBpbmNvbWUgZGF0YSBpcyBNTkFSLgoKIyMgV2h5IHdlIHNob3VsZCBjYXJlIGFib3V0IG1pc3NpbmduZXNzIHBhdHRlcm5zCgpJZiBkYXRhIGFyZSBNQ0FSIG9yIE1BUiwgdGhlbiB3ZSBkb24ndCBuZWVkIHRvIG1vZGVsIHRoZSBwcm9jZXNzIHRoYXQgZ2VuZXJhdGVzIHRoZSBtaXNzaW5nIGRhdGEgaW4gb3JkZXIgdG8gYWNjb21tb2RhdGUgdGhlIG1pc3NpbmcgZGF0YS4gVGhpcyBtZWFucyB0aGF0IHdoZW4gZGF0YSBhcmUgTUNBUiBvciBNQVIsIHRoZSAqKm1lY2hhbmlzbSoqIHRoYXQgcHJvZHVjZXMgdGhlIG1pc3NpbmcgZGF0YSBpcyAqKmlnbm9yYWJsZSoqLiBCdXQgd2hlbiBkYXRhIGFyZSBNTkFSLCB0aGUgbWVjaGFuaXNtIGlzICoqbm9uLWlnbm9yYWJsZSoqIGFuZCBpdCBiZWNvbWVzIG5lY2Vzc2FyeSB0byBtb2RlbCB0aGlzIG1lY2hhbmlzbSBpbiBvcmRlciB0byBkZWFsIHdpdGggdGhlIG1pc3NpbmduZXNzIGluIGEgdmFsaWQgd2F5LgoKRXZlbiBtb3JlIGRlcHJlc3NpbmdseSwgeW91IHJhcmVseSBpZiBldmVyIGNhbiB0ZXN0IHRvIHNlZSBpZiB5b3VyIGRhdGEgYXJlIE1DQVIsIE1BUiwgb3IgTU5BUiAqKmJlY2F1c2UgdGhlIGluZm9ybWF0aW9uIG5lZWRlZCB0byBtYWtlIHRoYXQgZGV0ZXJtaW5hdGlvbiBpcyBtaXNzaW5nKiouCgojIyBTaW11bGF0ZWQgZXhhbXBsZXMgb2YgbWlzc2luZ25lc3MgcGF0dGVybnMKCmBgYHtyIHNpbS1kYXRhfQpuX3NpbSA8LSAyNTAgIyBOdW1iZXIgb2YgcmFuZG9tIHNhbXBsZXMKCiMgVGFyZ2V0IHBhcmFtZXRlcnMgZm9yIHVuaXZhcmlhdGUgbm9ybWFsIGRpc3RyaWJ1dGlvbnMKcmhvIDwtIDIgLyAzCgptdTEgPC0gMTAKbXUyIDwtIDIwCgpzMSA8LSA5CnMyIDwtIDE2CnMxczIgPC0gc3FydChzMSkgKiBzcXJ0KHMyKSAqIHJobwoKIyBQYXJhbWV0ZXJzIGZvciBiaXZhcmlhdGUgbm9ybWFsIGRpc3RyaWJ1dGlvbgptdSA8LSBjKG11MSwgbXUyKSAjIE1lYW4gCnNpZ21hIDwtIG1hdHJpeChjKHMxLCBzMXMyLCBzMXMyLCBzMiksIDIpICMgQ292YXJpYW5jZSBtYXRyaXgKZGF0YV9zaW0gPC0gTUFTUzo6bXZybm9ybShuX3NpbSwgbXUsIHNpZ21hKSAlPiUKICBhc190aWJibGUgJT4lCiAgcmVuYW1lKHgxID0gVjEsCiAgICAgICAgIHgyID0gVjIpCmBgYAoKYGBge3Igc2ltLW1vZH0KIyBjb3JyZWxhdGlvbiBjb2VmZmljaWVudApjb3IoZGF0YV9zaW0pCgojIHJlZ3Jlc3Npb24gbW9kZWxzCmxtKHgyIH4geDEsIGRhdGEgPSBkYXRhX3NpbSkKbG0oeDEgfiB4MiwgZGF0YSA9IGRhdGFfc2ltKQoKIyBwbG90IG9mIGRhdGEKZ2dwbG90KGRhdGFfc2ltLCBhZXMoeDEsIHgyKSkgKwogIGdlb21fcG9pbnQoKSArCiAgZ2VvbV9zbW9vdGgobWV0aG9kID0gImxtIikgKwogIGxhYnModGl0bGUgPSAiQ29tcGxldGUgZGF0YSIsCiAgICAgICB4ID0gZXhwcmVzc2lvbihYWzFdKSwKICAgICAgIHkgPSBleHByZXNzaW9uKFhbMl0pKQpgYGAKCldoYXQgaGFwcGVucyB0byB0aGUgZGF0YSB1bmRlciB0aGUgdGhyZWUgbWVjaGFuaXNtcyBmb3IgZ2VuZXJhdGluZyBtaXNzaW5nIGRhdGE/CgpgYGB7ciBzaW0tbWNhcn0KbWNhciA8LSBkYXRhX3NpbSAlPiUKICBtdXRhdGUobmEgPSBpZmVsc2Uocm93X251bWJlcih4MikgJWluJSBzYW1wbGUoc2VxX2xlbihuX3NpbSksIDEwMCksIFRSVUUsIEZBTFNFKSkKCmdncGxvdChtY2FyLCBhZXMoeDEsIHgyKSkgKwogIGdlb21fcG9pbnQoYWVzKGFscGhhID0gbmEpKSArCiAgZ2VvbV9zbW9vdGgoZGF0YSA9IGZpbHRlcihtY2FyLCAhbmEpLAogICAgICAgICAgICAgIGFlcyhjb2xvciA9ICJOb24tbWlzc2luZyB2YWx1ZXMiKSwKICAgICAgICAgICAgICBtZXRob2QgPSAibG0iLCBzZSA9IEZBTFNFLCBmdWxscmFuZ2UgPSBUUlVFKSArCiAgZ2VvbV9zbW9vdGgoYWVzKGNvbG9yID0gIkFsbCB2YWx1ZXMiKSwKICAgICAgICAgICAgICBtZXRob2QgPSAibG0iLCBzZSA9IEZBTFNFLCBmdWxscmFuZ2UgPSBUUlVFKSArCiAgc2NhbGVfY29sb3JfYnJld2VyKHBhbGV0dGUgPSAiRGFyazIiKSArCiAgc2NhbGVfYWxwaGFfbWFudWFsKHZhbHVlcyA9IGMoLjMsIDEpKSArCiAgbGFicyh0aXRsZSA9ICJNaXNzaW5nIGNvbXBsZXRlbHkgYXQgcmFuZG9tIiwKICAgICAgIHggPSBleHByZXNzaW9uKFhbMV0pLAogICAgICAgeSA9IGV4cHJlc3Npb24oWFsyXSksCiAgICAgICBjb2xvciA9ICJSZWdyZXNzaW9uIGxpbmUiLAogICAgICAgYWxwaGEgPSAiTWlzc2luZyIpCmBgYAoKMTAwIG9ic2VydmF0aW9ucyBvbiAkWF8yJCBhcmUgc2VsZWN0ZWQgYXQgcmFuZG9tIGFuZCBzZXQgdG8gbWlzc2luZy4gSGVyZSB0aGUgbWlzc2luZyB2YWx1ZXMgb2YgJFhfMiQgYXJlIE1DQVIgYW5kIHRoZSBzdWJzZXQgb2YgdmFsaWQgb2JzZXJ2YXRpb25zIGlzIGEgc2ltcGxlIHJhbmRvbSBzYW1wbGUgb2YgdGhlIGZ1bGwgZGF0YSBzZXQuIFRoZSByZWdyZXNzaW9uIGxpbmUgd2l0aCBhbmQgd2l0aG91dCB0aGUgbWlzc2luZyB2YWx1ZXMgaXMgcmVsYXRpdmVseSBzaW1pbGFyLCB0aG91Z2ggc2xpZ2h0bHkgZGlmZmVyZW50IGR1ZSB0byB0aGUgbG93ZXIgc2FtcGxlIHNpemUgbmVlZGVkIHRvIGNhbGN1bGF0ZSB0aGUgcGFyYW1ldGVyIGVzdGltYXRlcyBhbmQgdGhlIHN0YW5kYXJkIGVycm9ycy4KCmBgYHtyIHNpbS1tYXJ9Cm1hciA8LSBkYXRhX3NpbSAlPiUKICBtdXRhdGUobmEgPSAuNSArICgyIC8gMykgKiAoeDEgLSAxMCkgKyBybm9ybShuX3NpbSwgc2QgPSAyKSwKICAgICAgICAgbmEgPSBsb2dpdDJwcm9iKG5hKSwKICAgICAgICAgbmEgPSBhcy5sb2dpY2FsKHJvdW5kKG5hKSkpCgpnZ3Bsb3QobWFyLCBhZXMoeDEsIHgyKSkgKwogIGdlb21fcG9pbnQoYWVzKGFscGhhID0gbmEpKSArCiAgZ2VvbV9zbW9vdGgoZGF0YSA9IGZpbHRlcihtYXIsICFuYSksCiAgICAgICAgICAgICAgYWVzKGNvbG9yID0gIk5vbi1taXNzaW5nIHZhbHVlcyIpLAogICAgICAgICAgICAgIG1ldGhvZCA9ICJsbSIsIHNlID0gRkFMU0UsIGZ1bGxyYW5nZSA9IFRSVUUpICsKICBnZW9tX3Ntb290aChhZXMoY29sb3IgPSAiQWxsIHZhbHVlcyIpLAogICAgICAgICAgICAgIG1ldGhvZCA9ICJsbSIsIHNlID0gRkFMU0UsIGZ1bGxyYW5nZSA9IFRSVUUpICsKICBzY2FsZV9jb2xvcl9icmV3ZXIocGFsZXR0ZSA9ICJEYXJrMiIpICsKICBzY2FsZV9hbHBoYV9tYW51YWwodmFsdWVzID0gYyguMywgMSkpICsKICBsYWJzKHRpdGxlID0gIk1pc3NpbmcgYXQgcmFuZG9tIiwKICAgICAgIHggPSBleHByZXNzaW9uKFhbMV0pLAogICAgICAgeSA9IGV4cHJlc3Npb24oWFsyXSksCiAgICAgICBjb2xvciA9ICJSZWdyZXNzaW9uIGxpbmUiLAogICAgICAgYWxwaGEgPSAiTWlzc2luZyIpCmBgYAoKSGVyZSBhbiBvYnNlcnZhdGlvbidzIG1pc3NpbmduZXNzIG9uICRYXzIkIGlzIHJlbGF0ZWQgdG8gaXRzIG9ic2VydmVkIHZhbHVlIG9mICRYXzEkIGluIHRoZSBsb2dpc3RpYyByZWdyZXNzaW9uIGZ1bmN0aW9uYWwgZm9ybToKCiQkXFByKFhfe2kyfSBcdGV4dHtpcyBtaXNzaW5nfSkgPSBcZnJhY3sxfXsxICsgXGV4cFtcZnJhY3sxfXsyfSArIFxmcmFjezJ9ezN9KFhfe2kxfSAtIDEwKV19JCQKCkFzICRYXzEkIGluY3JlYXNlcywgdGhlIHByb2JhYmlsaXR5IHRoYXQgJFhfMiQgaXMgbWlzc2luZyBpbmNyZWFzZXMuIEluIHRoZSByZXN1bHRpbmcgZGF0YXNldCwgYHIgc3VtKG1hciRuYSlgIG9ic2VydmF0aW9ucyBhcmUgbWlzc2luZy4gQmVjYXVzZSAkWF8xJCBhbmQgJFhfMiQgYXJlIHBvc2l0aXZlbHkgY29ycmVsYXRlZCwgdGhlcmUgYXJlIHJlbGF0aXZlbHkgZmV3ZXIgc21hbGwgdmFsdWVzIG9mICRYXzIkIGluIHRoZSBvYnNlcnZlZCBkYXRhIHZlcnN1cyB0aGUgY29tcGxldGUgZGF0YS4gSWYgd2Ugb25seSBsb29rIGF0IG9ic2VydmF0aW9ucyB3aXRoIHZhbGlkIGRhdGEgb24gYm90aCAkWF8xJCBhbmQgJFhfMiQsIHRoZW4gdGhpcyBzdWJzZXQgb2Ygb2JzZXJ2YXRpb25zIGFsc28gaGFzIHJlbGF0aXZlbHkgZmV3IHNtYWxsIHZhbHVlcyBvZiAkWF8xJC4gQnV0IGJlY2F1c2UgJFhfMSQgaXMgZnVsbHkgb2JzZXJ2ZWQsIHRoZSBtaXNzaW5nIGRhdGEgb24gJFhfMiQgYXJlIE1BUi4KCmBgYHtyIHNpbS1tbmFyfQptbmFyIDwtIGRhdGFfc2ltICU+JQogIG11dGF0ZShuYSA9IC41ICsgKDEgLyAyKSAqICh4MiAtIDIwKSArIHJub3JtKG5fc2ltLCBzZCA9IDIpLAogICAgICAgICBuYSA9IGxvZ2l0MnByb2IobmEpLAogICAgICAgICBuYSA9IGFzLmxvZ2ljYWwocm91bmQobmEpKSkKCmdncGxvdChtbmFyLCBhZXMoeDEsIHgyKSkgKwogIGdlb21fcG9pbnQoYWVzKGFscGhhID0gbmEpKSArCiAgZ2VvbV9zbW9vdGgoZGF0YSA9IGZpbHRlcihtbmFyLCAhbmEpLAogICAgICAgICAgICAgIGFlcyhjb2xvciA9ICJOb24tbWlzc2luZyB2YWx1ZXMiKSwKICAgICAgICAgICAgICBtZXRob2QgPSAibG0iLCBzZSA9IEZBTFNFLCBmdWxscmFuZ2UgPSBUUlVFKSArCiAgZ2VvbV9zbW9vdGgoYWVzKGNvbG9yID0gIkFsbCB2YWx1ZXMiKSwKICAgICAgICAgICAgICBtZXRob2QgPSAibG0iLCBzZSA9IEZBTFNFLCBmdWxscmFuZ2UgPSBUUlVFKSArCiAgc2NhbGVfY29sb3JfYnJld2VyKHBhbGV0dGUgPSAiRGFyazIiKSArCiAgc2NhbGVfYWxwaGFfbWFudWFsKHZhbHVlcyA9IGMoLjMsIDEpKSArCiAgbGFicyh0aXRsZSA9ICJNaXNzaW5nIG5vdCBhdCByYW5kb20iLAogICAgICAgeCA9IGV4cHJlc3Npb24oWFsxXSksCiAgICAgICB5ID0gZXhwcmVzc2lvbihYWzJdKSwKICAgICAgIGNvbG9yID0gIlJlZ3Jlc3Npb24gbGluZSIsCiAgICAgICBhbHBoYSA9ICJNaXNzaW5nIikKYGBgCgpGaW5hbGx5LCBoZXJlIGFuIG9ic2VydmF0aW9uJ3MgbWlzc2luZ25lc3Mgb24gJFhfMiQgaXMgcmVsYXRlZCB0byB0aGUgKHBvdGVudGlhbGx5KSB1bm9ic2VydmVkIHZhbHVlIG9mICRYXzIkIGl0c2VsZjoKCiQkXFByKFhfe2kyfSBcdGV4dHtpcyBtaXNzaW5nfSkgPSBcZnJhY3sxfXsxICsgXGV4cFtcZnJhY3sxfXsyfSArIFxmcmFjezF9ezJ9KFhfe2kyfSAtIDIwKV19JCQKCkFzICRYXzIkIGluY3JlYXNlcywgdGhlIHByb2JhYmlsaXR5IHRoYXQgJFhfMiQgaXMgbWlzc2luZyBpbmNyZWFzZXMuIEluIHRoZSByZXN1bHRpbmcgZGF0YXNldCwgYHIgc3VtKG1hciRuYSlgIG9ic2VydmF0aW9ucyBhcmUgbWlzc2luZy4gSGVyZSB0b28gdGhlcmUgYXJlIHJlbGF0aXZlbHkgZmV3IHNtYWxsIHZhbHVlcyBvZiAkWF8yJC4gQmVjYXVzZSBtaXNzaW5nbmVzcyBvbiAkWF8yJCBkZXBlbmRzIG9uIHRoZSB2YWx1ZSBvZiAkWF8yJCwgdGhlIG1pc3NpbmcgZGF0YSBhcmUgTU5BUi4gQnV0IGFnYWluLCB3ZSBvbmx5IGtub3cgdGhpcyBiZWNhdXNlIHdlIGdlbmVyYXRlZCB0aGUgbWlzc2luZ25lc3Mgb3Vyc2VsdmVzOyBpbiB0aGUgcmVhbCB3b3JsZCwgeW91IHJhcmVseSBjYW4gdmVyaWZ5IHRoaXMgcGF0dGVybiBvZiBtaXNzaW5nbmVzcy4KCiMgVHJhZGl0aW9uYWwgYXBwcm9hY2hlcyB0byBtaXNzaW5nIGRhdGEKCkluIGRlY2lkaW5nIGhvdyB0byBoYW5kbGUgbWlzc2luZ25lc3MsIHdlIHNob3VsZCBjb25zaWRlciB0aHJlZSBxdWVzdGlvbnM6CgoxLiBEb2VzIHRoZSBtZXRob2QgcHJvdmlkZSAqKmNvbnNpc3RlbnQgZXN0aW1hdGVzKiogb2YgdGhlIHBvcHVsYXRpb24gcGFyYW1ldGVycz8KMS4gRG9lcyB0aGUgbWV0aG9kIHByb3ZpZGUgKip2YWxpZCBzdGF0aXN0aWNhbCBpbmZlcmVuY2VzKio/CjEuIERvZXMgdGhlIG1ldGhvZCB1c2UgdGhlIG9ic2VydmVkIGRhdGEgKiplZmZpY2llbnRseSoqIG9yIGRvZXMgaXQgcmVja2xlc3NseSBkaXNjYXJkIGluZm9ybWF0aW9uPwoKIyMgRGlzY2FyZGluZyBkYXRhCgojIyMgQ29tcGxldGUtY2FzZSBhbmFseXNpcwoKKipDb21wbGV0ZS1jYXNlIGFuYWx5c2lzKiogKG9yICoqbGlzdHdpc2UqKiBvciAqKmNhc2V3aXNlKiogZGVsZXRpb24pIGlzIHByb2JhYmx5IHRoZSBtb3N0IGNvbW1vbiBhcHByb2FjaCBmb3IgaGFuZGxpbmcgbWlzc2luZyBkYXRhLiBJbiB0aGlzIG1ldGhvZCwgeW91IGlnbm9yZSBhbnkgb2JzZXJ2YXRpb25zIHdpdGggbWlzc2luZyB2YWx1ZXMgb24gdmFyaWFibGVzIG5lY2Vzc2FyeSB0byBlc3RpbWF0ZSB0aGUgbW9kZWwuCgpUaGUgYWR2YW50YWdlcyBvZiB0aGlzIG1ldGhvZCBhcmUgdGhhdCBpdDoKCiogSXMgc2ltcGxlCiogUHJvdmlkZXMgY29uc2lzdGVudCBlc3RpbWF0ZXMgYW5kIHZhbGlkIGluZmVyZW5jZXMgKip3aGVuIHRoZSBkYXRhIGlzIG1pc3NpbmcgY29tcGxldGVseSBhdCByYW5kb20qKgoqIFByb3ZpZGVzIGNvbnNpc3RlbnQgZXN0aW1hdGVzIG9mIHJlZ3Jlc3Npb24gY29lZmZpY2llbnRzIGFuZCB2YWxpZCBpbmZlcmVuY2VzIHdoZW4gbWlzc2luZ25lc3Mgb24gYWxsIHRoZSB2YXJpYWJsZXMgaW4gYSByZWdyZXNzaW9uIGRvZXMgbm90IGRlcGVuZCBvbiB0aGUgcmVzcG9uc2UgdmFyaWFibGUgKGV2ZW4gaWYgdGhlIGRhdGEgaXMgbm90IE1DQVIpCgpUaGUgZGlzYWR2YW50YWdlcyBvZiB0aGlzIG1ldGhvZCBhcmUgdGhhdCBpdDoKCiogRGlzY2FyZHMgdmFsdWFibGUgaW5mb3JtYXRpb24sIGRlY3JlYXNpbmcgZWZmaWNpZW5jeQoqIEJlY29tZXMgbGVzcyBlZmZpY2llbnQgYXMgbWlzc2luZ25lc3Mgb2NjdXJzIGluIG11bHRpcGxlIHZhcmlhYmxlcy4gRXZlbiBpZiBtaXNzaW5nbmVzcyBpcyBvbmx5IDUlIGZvciBlYWNoIGluZGl2aWR1YWwgdmFyaWFibGUsIGZvciBhIGRhdGFzZXQgd2l0aCAxMCB2YXJpYWJsZXMgd2Ugd291bGQgZXhwZWN0IG9ubHkgJDEwMCBcdGltZXMgLjk1XnsxMH0gPSA2MCUkIG9mIHRoZSBvYnNlcnZhdGlvbnMgdG8gYmUgdXNhYmxlCiogV2hlbiBkYXRhIGlzIE1BUiBvciBNTkFSLCBsaXN0d2lzZSBkZWxldGlvbiBwcm92aWRlcyBiaWFzZWQgcmVzdWx0cyBhbmQgaW52YWxpZCBpbmZlcmVuY2VzCgojIyMgQXZhaWxhYmxlLWNhc2UgYW5hbHlzaXMKCioqQXZhaWxhYmxlLWNhc2UgYW5hbHlzaXMqKiAob3IgKipwYWlyd2lzZSBkZWxldGlvbioqKSB1c2VzIGFsbCBub24gbWlzc2luZyBvYnNlcnZhdGlvbnMgdG8gY29tcHV0ZSBlYWNoIHN0YXRpc3RpYyBvZiBpbnRlcmVzdC4gSW4gT0xTLCB0aGlzIG1lYW5zIGVzdGltYXRpbmcgdGhlIHJlZ3Jlc3Npb24gY29lZmZpY2llbnRzIGZyb20gdGhlIG1lYW5zLCB2YXJpYW5jZXMsIGFuZCBjb3ZhcmlhbmNlcyBvZiB0aGUgdmFyaWFibGVzIHJhdGhlciB0aGFuIGRpcmVjdGx5IGZyb20gdGhlIG9ic2VydmF0aW9ucy4gV2hpbGUgdGhpcyBhcHBlYXJzIHRvIHVzZSBtb3JlIGluZm9ybWF0aW9uIHRoYW4gY29tcGxldGUtY2FzZSBhbmFseXNpcywgaXQgY2FuIHNvbWV0aW1lcyBiZSAqbGVzcyBlZmZpY2llbnQqLiBBbmQgYnkgYmFzaW5nIGVhY2ggc3RhdGlzdGljIG9mIGludGVyZXN0IG9uIGRpZmZlcmVudCBzdWJzZXRzIG9mIHRoZSBkYXRhLCByZXN1bHRzIGNhbiBiZWNvbWUgbm9uc2Vuc2ljYWwgKGUuZy4gY29ycmVsYXRpb25zIG91dHNpZGUgb2YgdGhlICRbLTEsICsxXSQgcmFuZ2UpLiBGaW5hbGx5LCB0aGlzIG1ldGhvZCBpcyBtdWNoIG1vcmUgZGlmZmljdWx0IHRvIGltcGxlbWVudCBvdXRzaWRlIG9mIE9MUyB0byBvdGhlciBHTE1zLgoKIyMgSW1wdXRhdGlvbgoKKipJbXB1dGF0aW9uKiogcmVmZXJzIHRvIGZpbGxpbmcgaW4gbWlzc2luZyBkYXRhIHdpdGggcGxhdXNpYmxlICoqaW1wdXRlZCoqIHZhbHVlcy4gVGhlIGNvbXBsZXRlZCBkYXRhIHNldCBpcyB0aGVuIGFuYWx5emVkIHVzaW5nIHRyYWRpdGlvbmFsIG1ldGhvZHMuCgojIyMgVW5jb25kaXRpb25hbCBtZWFuIGltcHV0YXRpb24KCioqVW5jb25kaXRpb25hbCBtZWFuIGltcHV0YXRpb24qKiByZXBsYWNlcyB0aGUgbWlzc2luZyB2YWx1ZSB3aXRoIHRoZSBhcml0aG1ldGljIG1lYW4gb2YgdGhlIG9ic2VydmVkIHZhbHVlcyBmb3IgdGhlIHZhcmlhYmxlIGluIHF1ZXN0aW9uLiBEb2luZyBzbyBwcmVzZXJ2ZXMgdGhlIG1lYW4gb2YgdGhlIHZhcmlhYmxlLCBidXQgZGVjcmVhc2VzIGl0cyB2YXJpYW5jZSBhbmQgaXRzIGNvdmFyaWFuY2Ugd2l0aCBvdGhlciB2YXJpYWJsZXMuIFRoaXMgY2FuIGxlYWQgdG8gYmlhc2VkIHJlZ3Jlc3Npb24gY29lZmZpY2llbnRzIGFuZCBpbnZhbGlkIGluZmVyZW5jZXMgZXZlbiBpZiB0aGUgZGF0YSBpcyBNQ0FSLgoKIyMjIENvbmRpdGlvbmFsLW1lYW4gaW1wdXRhdGlvbgoKKipDb25kaXRpb25hbC1tZWFuIGltcHV0YXRpb24qKiByZXBsYWNlcyBtaXNzaW5nIGRhdGEgd2l0aCBwcmVkaWN0ZWQgdmFsdWVzIG9idGFpbmVkIGZyb20gYSBzdGF0aXN0aWNhbCBsZWFybmluZyBtb2RlbCwgdHlwaWNhbGx5IGEgcmVncmVzc2lvbiBtb2RlbC4gVXNpbmcgdGhlIGF2YWlsYWJsZSBkYXRhLCByZWdyZXNzIGVhY2ggdmFyaWFibGUgd2l0aCBtaXNzaW5nIGRhdGEgb24gdGhlIG90aGVyIHZhcmlhYmxlcyBpbiB0aGUgZGF0YSBzZXQuIFRoZW4gdXNlIHRoZSByZWdyZXNzaW9uIG1vZGVsIHRvIGdlbmVyYXRlIHByZWRpY3RlZCB2YWx1ZXMgZm9yIHRoZSBtaXNzaW5nIGRhdGEgaW4gdGhlIHJlZ3Jlc3NlZCB2YXJpYWJsZS4gSG93ZXZlciB0aGlzIHN0aWxsIGxlYXZlcyB0d28gcHJvYmxlbXM6CgoxLiBJbXB1dGVkIHZhbHVlcyBzdGlsbCB0ZW5kIHRvIGJlIGxlc3MgdmFyaWFibGUgdGhhbiB0aGUgcmVhbCBkYXRhIGJlY2F1c2UgdGhleSBsYWNrICoqcmVzaWR1YWwgdmFyaWF0aW9uKioKMS4gV2Ugc3RpbGwgZmFpbCB0byBhY2NvdW50IGZvciB1bmNlcnRhaW50eSBpbiB0aGUgZXN0aW1hdGVzIG9mIHRoZSByZWdyZXNzaW9uIGNvZWZmaWNpZW50cyB1c2VkIHRvIG9idGFpbiB0aGUgaW1wdXRlZCB2YWx1ZXMKCkhvdyBkbyBhbGwgb2YgdGhlc2UgbWV0aG9kcyBzdGFjayB1cD8KCmBgYHtyIGNvbXBhcmUtaW1wdXRhdGlvbiwgZmlnLnNob3cgPSAiaGlkZSJ9CmdldF9taXNzX3N0YXQgPC0gZnVuY3Rpb24oZGYpewogIGRmICU+JQogICAgc3VtbWFyaXplKG11XzEgPSBtZWFuKHgxLCBuYS5ybSA9IFRSVUUpLAogICAgICAgICAgICAgIG11XzIgPSBtZWFuKHgyLCBuYS5ybSA9IFRSVUUpLAogICAgICAgICAgICAgIHNpZ21hXzEgPSB2YXIoeDEsIHVzZSA9ICJjb21wbGV0ZS5vYnMiKSwKICAgICAgICAgICAgICBzaWdtYV8yID0gdmFyKHgyLCB1c2UgPSAiY29tcGxldGUub2JzIiksCiAgICAgICAgICAgICAgc2lnbWFfMTIgPSBjb3YoLiwgdXNlID0gImNvbXBsZXRlLm9icyIpWzEsIDJdLAogICAgICAgICAgICAgIHJobyA9IGNvciguLCB1c2UgPSAiY29tcGxldGUub2JzIilbMSwgMl0sCiAgICAgICAgICAgICAgYmV0YV8xMiA9IGxtKHgyIH4geDEsIGRhdGEgPSAuKSAlPiUgY29lZiguKSAlPiUgLltbMl1dLAogICAgICAgICAgICAgIGJldGFfMjEgPSBsbSh4MSB+IHgyLCBkYXRhID0gLikgJT4lIGNvZWYoLiklPiUgLltbMl1dCiAgICApCn0KCmRhdGFfbWlzcyA8LSBsaXN0KAogIG1jYXIgPSBkYXRhX3NpbSAlPiUKICAgIG11dGF0ZSh4MiA9IHJlcGxhY2UoeDIsIHNhbXBsZShzZXFfbGVuKG5fc2ltKSwgMTAwKSwgTkEpKSwKICBtYXIgPSBkYXRhX3NpbSAlPiUKICAgIG11dGF0ZShuYSA9IC41ICsgKDIgLyAzKSAqICh4MSAtIDEwKSArIHJub3JtKG5fc2ltLCBzZCA9IDIpLAogICAgICAgICAgIG5hID0gbG9naXQycHJvYihuYSksCiAgICAgICAgICAgbmEgPSBhcy5sb2dpY2FsKHJvdW5kKG5hKSksCiAgICAgICAgICAgeDIgPSByZXBsYWNlKHgyLCBuYSwgTkEpKSAlPiUKICAgIHNlbGVjdCgtbmEpLAogIG1uYXIgPSBkYXRhX3NpbSAlPiUKICAgIG11dGF0ZShuYSA9IC41ICsgKDEgLyAyKSAqICh4MiAtIDIwKSArIHJub3JtKG5fc2ltLCBzZCA9IDIpLAogICAgICAgICAgIG5hID0gbG9naXQycHJvYihuYSksCiAgICAgICAgICAgbmEgPSBhcy5sb2dpY2FsKHJvdW5kKG5hKSksCiAgICAgICAgICAgeDIgPSByZXBsYWNlKHgyLCBuYSwgTkEpKSAlPiUKICAgIHNlbGVjdCgtbmEpCikKCiMgY29tcGxldGUgY2FzZXMKY29tcGxldGVfY2FzZXMgPC0gZGF0YV9taXNzICU+JQogIG1hcChuYS5vbWl0KSAlPiUKICBtYXBfZGYoZ2V0X21pc3Nfc3RhdCwgLmlkID0gImlkIikKCiMgYXZhaWxhYmxlIGNhc2VzCmF2YWlsYWJsZV9jYXNlcyA8LSBkYXRhX21pc3MgJT4lCiAgbWFwX2RmKH4gLnggJT4lCiAgICBzdW1tYXJpemUobXVfMSA9IG1lYW4oeDEsIG5hLnJtID0gVFJVRSksCiAgICAgICAgICAgICAgbXVfMiA9IG1lYW4oeDIsIG5hLnJtID0gVFJVRSksCiAgICAgICAgICAgICAgc2lnbWFfMSA9IHZhcih4MSwgdXNlID0gImNvbXBsZXRlLm9icyIpLAogICAgICAgICAgICAgIHNpZ21hXzIgPSB2YXIoeDIsIHVzZSA9ICJjb21wbGV0ZS5vYnMiKSwKICAgICAgICAgICAgICBzaWdtYV8xMiA9IGNvdiguLCB1c2UgPSAicGFpcndpc2UuY29tcGxldGUub2JzIilbMSwgMl0sCiAgICAgICAgICAgICAgcmhvID0gY29yKC4sIHVzZSA9ICJwYWlyd2lzZS5jb21wbGV0ZS5vYnMiKVsxLCAyXSwKICAgICAgICAgICAgICBiZXRhXzEyID0gcHN5Y2g6Om1hdC5yZWdyZXNzKCJ4MiIsICJ4MSIsCiAgICAgICAgICAgICAgICAgICBkYXRhID0gY292KC4sIHVzZSA9ICJwYWlyd2lzZS5jb21wbGV0ZS5vYnMiKSwKICAgICAgICAgICAgICAgICAgIG4ub2JzID0gbnJvdyhuYS5vbWl0KC4pKSkgJT4lCiAgICAgICAgICAgICAgICAuJGJldGEgJT4lCiAgICAgICAgICAgICAgICAuW1sxXV0sCiAgICAgICAgICAgICAgYmV0YV8yMSA9IHBzeWNoOjptYXQucmVncmVzcygieDEiLCAieDIiLAogICAgICAgICAgICAgICAgICAgZGF0YSA9IGNvdiguLCB1c2UgPSAicGFpcndpc2UuY29tcGxldGUub2JzIiksCiAgICAgICAgICAgICAgICAgICBuLm9icyA9IG5yb3cobmEub21pdCguKSkpICU+JQogICAgICAgICAgICAgICAgLiRiZXRhICU+JQogICAgICAgICAgICAgICAgLltbMV1dCiAgICApLCAuaWQgPSAiaWQiKQoKIyBtZWFuIGltcHV0YXRpb24KbWVhbl9pbXAgPC0gZGF0YV9taXNzICU+JQogIG1hcCh+IG11dGF0ZSgueCwgeDIgPSBpZmVsc2UoaXMubmEoeDIpLCBtZWFuKHgyLCBuYS5ybSA9IFRSVUUpLCB4MikpKSAlPiUKICBtYXBfZGYoZ2V0X21pc3Nfc3RhdCwgLmlkID0gImlkIikKCiMgcmVncmVzc2lvbiBpbXB1dGF0aW9uCnJlZ19pbXAgPC0gZGF0YV9taXNzICU+JQogIG1hcCh+IC54ICU+JQogICAgICAgIG11dGF0ZSh4Ml9pbXAgPSBsbSh4MiB+IHgxLCBkYXRhID0gLikgJT4lCiAgICAgICAgICAgICAgICAgcHJlZGljdCguLCBuZXdkYXRhID0gLngpLAogICAgICAgICAgICAgICB4MiA9IGlmZWxzZShpcy5uYSh4MiksIHgyX2ltcCwgeDIpKSkgJT4lCiAgbWFwX2RmKGdldF9taXNzX3N0YXQsIC5pZCA9ICJpZCIpCgpzdW1fc3RhdHMgPC0gYmluZF9yb3dzKAogIGBQb3B1bGF0aW9uIHBhcmFtZXRlcnNgID0gZGF0YV9mcmFtZSgKICAgIG11XzEgPSAxMCwKICAgIG11XzIgPSAyMCwKICAgIHNpZ21hXzEgPSA5LAogICAgc2lnbWFfMiA9IDE2LAogICAgc2lnbWFfMTIgPSA4LAogICAgcmhvID0gLjY2NywKICAgIGJldGFfMTIgPSAuNSwKICAgIGJldGFfMjEgPSAuODg5KSwKICBgQ29tcGxldGUgZGF0YWAgPSBnZXRfbWlzc19zdGF0KGRhdGFfc2ltKSwKICBgQ29tcGxldGUgY2FzZXNgID0gY29tcGxldGVfY2FzZXMsCiAgYEF2YWlsYWJsZSBjYXNlc2AgPSBhdmFpbGFibGVfY2FzZXMsCiAgYE1lYW4gaW1wdXRhdGlvbmAgPSBtZWFuX2ltcCwKICBgUmVncmVzc2lvbiBpbXB1dGF0aW9uYCA9IHJlZ19pbXAsCiAgLmlkID0gIm1ldGhvZCIKKSAlPiUKICBtdXRhdGUoaWQgPSBpZmVsc2UobWV0aG9kID09ICJQb3B1bGF0aW9uIHBhcmFtZXRlcnMiLCAiUGFyYW1ldGVyIiwgaWQpLAogICAgICAgICBpZCA9IGlmZWxzZShtZXRob2QgPT0gIkNvbXBsZXRlIGRhdGEiLCAiQ29tcGxldGUgZGF0YSIsIGlkKSkgJT4lCiAgZ2F0aGVyKHBhcmFtLCB2YWx1ZSwgLW1ldGhvZCwgLWlkKSAlPiUKICBtdXRhdGUocGFyYW0gPSBpZmVsc2UocGFyYW0gPT0gIm11XzEiLCAibXVbMV0iLCBwYXJhbSksCiAgICAgICAgIHBhcmFtID0gaWZlbHNlKHBhcmFtID09ICJtdV8yIiwgIm11WzJdIiwgcGFyYW0pLAogICAgICAgICBwYXJhbSA9IGlmZWxzZShwYXJhbSA9PSAic2lnbWFfMSIsICJzaWdtYVsxXV4yIiwgcGFyYW0pLAogICAgICAgICBwYXJhbSA9IGlmZWxzZShwYXJhbSA9PSAic2lnbWFfMiIsICJzaWdtYVsyXV4yIiwgcGFyYW0pLAogICAgICAgICBwYXJhbSA9IGlmZWxzZShwYXJhbSA9PSAic2lnbWFfMTIiLCAic2lnbWFbMTJdIiwgcGFyYW0pLAogICAgICAgICBwYXJhbSA9IGlmZWxzZShwYXJhbSA9PSAiYmV0YV8xMiIsICJiZXRhWzEyXSIsIHBhcmFtKSwKICAgICAgICAgcGFyYW0gPSBpZmVsc2UocGFyYW0gPT0gImJldGFfMjEiLCAiYmV0YVsyMV0iLCBwYXJhbSkpCmBgYAoKYGBge3IgY29tcGFyZS1pbXB1dGF0aW9uLXBsb3R9CnN1bV9zdGF0cyAlPiUKICBmaWx0ZXIoaWQgJWluJSBjKCJtY2FyIiwgIm1hciIsICJtbmFyIikpICU+JQogIG11dGF0ZShpZCA9IGZhY3RvcihpZCwgbGV2ZWxzID0gYygibWNhciIsICJtYXIiLCAibW5hciIpLAogICAgICAgICAgICAgICAgICAgICBsYWJlbHMgPSBjKCJNQ0FSIiwgIk1BUiIsICJNTkFSIikpKSAlPiUKICBnZ3Bsb3QoYWVzKGlkLCB2YWx1ZSwgY29sb3IgPSBtZXRob2QsIHNoYXBlID0gbWV0aG9kKSkgKwogIGZhY2V0X3dyYXAoIH4gcGFyYW0sIG5yb3cgPSAyLCBzY2FsZXMgPSAiZnJlZV95IiwKICAgICAgICAgICAgICBsYWJlbGxlciA9ICJsYWJlbF9wYXJzZWQiKSArCiAgZ2VvbV9wb2ludCgpICsKICBnZW9tX2hsaW5lKGRhdGEgPSBmaWx0ZXIoc3VtX3N0YXRzLCBpZCA9PSAiUGFyYW1ldGVyIiksCiAgICAgICAgICAgICBhZXMoeWludGVyY2VwdCA9IHZhbHVlKSkgKwogIHNjYWxlX2NvbG9yX2JyZXdlcih0eXBlID0gInF1YWwiLCBwYWxldHRlID0gIkRhcmsyIiwgCiAgICAgICAgICAgICAgICAgICAgIGd1aWRlID0gZ3VpZGVfbGVnZW5kKG5yb3cgPSAyKSkgKwogIGxhYnMoeCA9IE5VTEwsCiAgICAgICB5ID0gIkVzdGltYXRlZCBwYXJhbWV0ZXIgdmFsdWUiLAogICAgICAgY29sb3IgPSBOVUxMLAogICAgICAgc2hhcGUgPSBOVUxMKSArCiAgdGhlbWUoYXhpcy50ZXh0LnggPSBlbGVtZW50X3RleHQoYW5nbGUgPSAzMCksCiAgICAgICAgbGVnZW5kLnBvc2l0aW9uID0gImJvdHRvbSIpCmBgYAoKIyBJbXB1dGF0aW9uIGVzdGltYXRpb24gc3RyYXRlZ2llcwoKIyMgTWF4aW11bS1saWtlbGlob29kIGVzdGltYXRpb24gZm9yIGRhdGEgTUFSCgpXaGVuIGRhdGEgYXJlIE1BUiAob3IgTUNBUiksIHdlIGNhbiB1c2UgbWF4aW11bS1saWtlbGlob29kIGVzdGltYXRpb24gdG8gZXN0aW1hdGUgdGhlIHBhcmFtZXRlcnMgb2YgaW50ZXJlc3QgYW5kIGdlbmVyYXRlIGltcHV0ZWQgdmFsdWVzIGZvciB0aGUgbWlzc2luZyBkYXRhLiBUaGlzIHJlcXVpcmVzIHNldmVyYWwgYXNzdW1wdGlvbnMgYWJvdXQgdGhlIG1pc3NpbmduZXNzIG1lY2hhbmlzbSBhbmQgdGhlIGRpc3RyaWJ1dGlvbiBvZiB0aGUgY29tcGxldGUgZGF0YS4KCkxldCAkcChcbWF0aGJme1h9LCBcdGhldGEpID0gcChcbWF0aGJme1h9X3tcdGV4dHtvYnN9fSwgXG1hdGhiZntYfV97XHRleHR7bWlzfX07IFx0aGV0YSkkIHJlcHJlc2VudCB0aGUgam9pbnQgcHJvYmFiaWxpdHkgZGVuc2l0eSBmb3IgdGhlIGNvbXBsZXRlIGRhdGEgJFxtYXRoYmZ7WH0kLCB3aGljaCBpcyBjb21wb3NlZCBvZiB0aGUgb2JzZXJ2ZWQgYW5kIG1pc3NpbmcgY29tcG9uZW50cyBkZW5vdGVkIGJ5ICRcbWF0aGJme1h9X3tcdGV4dHtvYnN9fSwgXG1hdGhiZntYfV97XHRleHR7bWlzfX0kLiBUaGUgdmVjdG9yICRcdGhldGEkIGNvbnRhaW5zIHRoZSB1bmtub3duIHBhcmFtZXRlcnMgb24gd2hpY2ggdGhlIGNvbXBsZXRlLWRhdGEgZGlzdHJpYnV0aW9uIGRlcGVuZHMuIEZvciBleGFtcGxlLCBpZiB0aGUgdmFyaWFibGVzIGluICRcbWF0aGJme1h9JCBhcmUgbXVsdGl2YXJpYXRlIG5vcm1hbGx5IGRpc3RyaWJ1dGVkLCB0aGVuICRcdGhldGEkIGluY2x1ZGVzIHRoZSBwb3B1bGF0aW9uIG1lYW5zIGFuZCBjb3ZhcmlhbmNlcyBhbW9uZyB0aGUgdmFyaWFibGVzLgoKSWYgZGF0YSBpcyBNQVIsIHRoZW4gdGhlIE1MIGVzdGltYXRlICRcaGF0e1x0aGV0YX0kIG9mICRcdGhldGEkIGNhbiBiZSBvYnRhaW5lZCBmcm9tIHRoZSBtYXJnaW5hbCBkaXN0cmlidXRpb24gb2YgdGhlIG9ic2VydmVkIGRhdGEgYnkgaW50ZWdyYXRpbmcgb3ZlciB0aGUgbWlzc2luZyBkYXRhOgoKJCRwKFxtYXRoYmZ7WH1fXHRleHR7b2JzfTsgXHRoZXRhKSA9IFxpbnR7cChcbWF0aGJme1h9X3tcdGV4dHtvYnN9fSwgXG1hdGhiZntYfV97XHRleHR7bWlzfX07IFx0aGV0YSl9IGRcbWF0aGJme1h9X3tcdGV4dHttaXN9fSQkCgpXZSdsbCBza2lwIHRoZSBtYXRoIGZvciBhbGwgb2YgdGhpc15bU2VlIEZveCBjaCAyMC4zIGZvciB0aGUgZ29yeSBkZXRhaWxzXSwgYnV0IHRoZSBpbXBvcnRhbnQgdGhpbmcgdG8gbm90ZSBpcyB0aGF0IHRoZSBNTCBlc3RpbWF0ZSBvbmx5IGhhcyBhIGNsb3NlZC1mb3JtIHNvbHV0aW9uIHdoZW4gbWlzc2luZ25lc3MgZm9sbG93cyBhbiBhcmJpdHJhcnkgcGF0dGVybiAoaS5lLiBNQVIpLiBXZSBjYW4gdXNlIGl0ZXJhdGl2ZSBwcm9jZXNzZXMgc3VjaCBhcyBhbiAqKmV4cGVjdGF0aW9uLW1heGltaXphdGlvbiAoRU0pIGFsZ29yaXRobSoqIHRvIGZpbmQgdGhlIE1MIGVzdGltYXRlcyBpbiB0aGUgYWJzZW5jZSBvZiBhcmJpdHJhcnkgcGF0dGVybnMgb2YgbWlzc2luZ25lc3MuIFR5cGljYWxseSBzb2Z0d2FyZSB3aWxsIHVzZSBhbiAqKmV4cGVjdGF0aW9uLW1heGltaXphdGlvbiAoRU0pIGFsZ29yaXRobSoqIHRvIGZpbmQgdGhlIE1MIGVzdGltYXRlcyBpbiB0aGUgYWJzZW5jZSBvZiBhcmJpdHJhcnkgcGF0dGVybnMgb2YgbWlzc2luZ25lc3MuIFdoZW4gdGhlIHBhcmFtZXRlciBlc3RpbWF0ZXMgc3RvcCBjaGFuZ2luZyBmcm9tIG9uZSBpdGVyYXRpb24gdG8gdGhlIG5leHQsIHRoZXkgY29udmVyZ2UgdG8gdGhlIE1MIGVzdGltYXRlcyAkXGhhdHtcdGhldGF9JC4KCiogSW1wbGVtZW50ZWQgaW4gYEFtZWxpYWAgZm9yIFIKCiMjIFByZWRpY3RpdmUgbWVhbiBtYXRjaGluZwoKQ29tYmluZXMgcmVncmVzc2lvbiBtb2RlbCB3aXRoIG1hdGNoaW5nIHByb2NlZHVyZS4KCjEuIEZvciBjYXNlcyB3aXRoIG5vIG1pc3NpbmcgZGF0YSwgZXN0aW1hdGUgYSBsaW5lYXIgcmVncmVzc2lvbiBvZiAkeCQgb24gJHokLCBwcm9kdWNpbmcgYSBzZXQgb2YgY29lZmZpY2llbnRzICRiJC4KMS4gTWFrZSBhIHJhbmRvbSBkcmF3IGZyb20gdGhlICoqcG9zdGVyaW9yIHByZWRpY3RpdmUgZGlzdHJpYnV0aW9uKiogb2YgJGIkLCBwcm9kdWNpbmcgYSBuZXcgc2V0IG9mIGNvZWZmaWNpZW50cyAkYiokLiBUeXBpY2FsbHkgdGhpcyB3b3VsZCBiZSBhIHJhbmRvbSBkcmF3IGZyb20gYSBtdWx0aXZhcmlhdGUgbm9ybWFsIGRpc3RyaWJ1dGlvbiB3aXRoIG1lYW4gJGIkIGFuZCB0aGUgZXN0aW1hdGVkIGNvdmFyaWFuY2UgbWF0cml4IG9mICRiJCAod2l0aCBhbiBhZGRpdGlvbmFsIHJhbmRvbSBkcmF3IGZvciB0aGUgcmVzaWR1YWwgdmFyaWFuY2UpLiBUaGlzIHN0ZXAgaXMgbmVjZXNzYXJ5IHRvIHByb2R1Y2Ugc3VmZmljaWVudCB2YXJpYWJpbGl0eSBpbiB0aGUgaW1wdXRlZCB2YWx1ZXMsIGFuZCBpcyBjb21tb24gdG8gYWxsIHJvYnVzdCBtZXRob2RzIGZvciBtdWx0aXBsZSBpbXB1dGF0aW9uLgoxLiBVc2luZyAkYiokLCBnZW5lcmF0ZSBwcmVkaWN0ZWQgdmFsdWVzIGZvciAkeCQgZm9yIGFsbCBjYXNlcywgYm90aCB0aG9zZSB3aXRoIGRhdGEgbWlzc2luZyBvbiAkeCQgYW5kIHRob3NlIHdpdGggZGF0YSBwcmVzZW50LgoxLiBGb3IgZWFjaCBjYXNlIHdpdGggbWlzc2luZyAkeCQsIGlkZW50aWZ5IGEgc2V0IG9mIGNhc2VzIHdpdGggb2JzZXJ2ZWQgJHgkIHdob3NlIHByZWRpY3RlZCB2YWx1ZXMgYXJlIGNsb3NlIHRvIHRoZSBwcmVkaWN0ZWQgdmFsdWUgZm9yIHRoZSBjYXNlIHdpdGggbWlzc2luZyBkYXRhLgoxLiBGcm9tIGFtb25nIHRob3NlIGNsb3NlIGNhc2VzLCByYW5kb21seSBjaG9vc2Ugb25lIGFuZCBhc3NpZ24gaXRzIG9ic2VydmVkIHZhbHVlIHRvIHN1YnN0aXR1dGUgZm9yIHRoZSBtaXNzaW5nIHZhbHVlLgoKU3RlcCAyIGRpc3Rpbmd1aXNoZXMgdGhpcyBmcm9tIHB1cmUgY29uZGl0aW9uYWwtbWVhbiBpbXB1dGF0aW9uIGJ5IGFjY291bnRpbmcgZm9yIGFkZGl0aW9uYWwgdW5jZXJ0YWludHkgaW4gdGhlIG1vZGVsKHMpLiBCeSBkZWZhdWx0IGluIG1vc3Qgc29mdHdhcmUsICRrPTUkIGlzIHRoZSBudW1iZXIgb2YgbWF0Y2hlcyBvYnNlcnZhdGlvbnMgZnJvbSB3aGljaCB0byBkcmF3IHRoZSBpbXB1dGVkIHZhbHVlLgoKKiBJbXBsZW1lbnRlZCBpbiBgbWljZWAgYW5kIGBtaWAgZm9yIFIKCiMjIFJhbmRvbSBmb3Jlc3QgbW9kZWwKCkFsdGVybmF0aXZlbHksIHdlIGNhbiB1c2UgYSByYW5kb20gZm9yZXN0IGFsZ29yaXRobSB0byBwdXJzdWUgYSBub24tcGFyYW1ldHJpYyBpbXB1dGF0aW9uIHN0cmF0ZWd5LiBJbiB0aGlzIGFwcHJvYWNoLCB5b3UgYnVpbGQgYSByYW5kb20gZm9yZXN0IG1vZGVsIGZvciBlYWNoIHZhcmlhYmxlIHRvIHByZWRpY3QgaXRzIHZhbHVlIHVzaW5nIHRoZSBvdGhlciB2YXJpYWJsZXMgaW4gdGhlIGRhdGFzZXQuIFRoZXNlIHJlc3VsdHMgYXJlIHVzZWQgdG8gZ2VuZXJhdGUgaW1wdXRlZCB2YWx1ZXMgZm9yIGFsbCB0aGUgbWlzc2luZyB2YXJpYWJsZXMgYW5kIG9ic2VydmF0aW9ucy4KCiogSW1wbGVtZW50ZWQgaW4gYG1pc3NGb3Jlc3RgIGZvciBSCgojIyBEZWVwIGxlYXJuaW5nCgpCZWNhdXNlIGV2ZXJ5b25lIGlzIGRvaW5nIGl0LiBObyBzdGFibGUgcGFja2FnZXMgaW1wbGVtZW50IG9mZi10aGUtc2hlbGYgbWV0aG9kcywgYnV0IHJlc2VhcmNoZXJzIGFyZSBkZXZlbG9waW5nIGRlZXAgbGVhcm5pbmcgbWV0aG9kcyBmb3IgaW1wdXRpbmcgbWlzc2luZyB2YWx1ZXMuCgojIEdlbmVyYXRpbmcgbXVsdGlwbGUgaW1wdXRhdGlvbnMKCioqQmF5ZXNpYW4gbXVsdGlwbGUgaW1wdXRhdGlvbioqIChNSSkgaXMgYSBmbGV4aWJsZSBtZXRob2QgZm9yIGRlYWxpbmcgd2l0aCBtaXNzaW5nIGRhdGEgTUFSLiBJdCBzdGFydHMgYnkgc3BlY2lmeWluZyB0aGUgZGlzdHJpYnV0aW9uIG9mIHRoZSBjb21wbGV0ZSBkYXRhOyB0eXBpY2FsbHkgdGhlIGRhdGEgaXMgYXNzdW1lZCB0byBiZSBtdWx0aXZhcmlhdGUgbm9ybWFsLiBUaGUga2V5IGRpZmZlcmVuY2UgaXMgdGhhdCB0aGlzIG1ldGhvZCByZWZsZWN0cyB1bmNlcnRhaW50eSBhc3NvY2lhdGVkIHdpdGggbWlzc2luZyBkYXRhIGJ5IGltcHV0aW5nIG11bHRpcGxlIHZhbHVlcyBmb3IgZWFjaCBtaXNzaW5nIGRhdGEgdmFsdWUgKGkuZS4gbXVsdGlwbGUgaW1wdXRhdGlvbiksIHByb2R1Y2luZyBzZXZlcmFsIGNvbXBsZXRlIGRhdGFzZXRzLiBFYWNoIGRhdGFzZXQgaXMgdGhlbiBhbmFseXplZCBpbmRlcGVuZGVudGx5IGFuZCBpbiBwYXJhbGxlbCwgZXN0aW1hdGluZyBwYXJhbWV0ZXJzIG9mIGludGVyZXN0IGFuZCBzdGFuZGFyZCBlcnJvcnMgZm9yIGVhY2ggaW1wdXRlZCBkYXRhc2V0LiBUaGUgZXN0aW1hdGVkIHBhcmFtZXRlcnMgYXJlIHRoZW4gYXZlcmFnZWQgdG9nZXRoZXIgYWNyb3NzIHRoZSBpbXB1dGVkIGRhdGFzZXRzLiBTdGFuZGFyZCBlcnJvcnMgYXJlIGFsc28gY29tYmluZWQsIHRha2luZyBpbnRvIGFjY291bnQgdGhlIHZhcmlhdGlvbiBhbW9uZyB0aGUgZXN0aW1hdGVzIGluIHRoZSBzZXZlcmFsIGRhdGFzZXRzIGFuZCBjYXB0dXJpbmcgdGhlIGFkZGVkIHVuY2VydGFpbnR5IGR1ZSB0byBoYXZpbmcgdG8gZGVhbCB3aXRoIG1pc3NpbmcgZGF0YS4KClRoZSBtZXRob2QgaXMgKipCYXllc2lhbioqIGJlY2F1c2UgZWFjaCBlc3RpbWF0ZSBvZiB0aGUgcGFyYW1ldGVycyBhbmQgc3RhbmRhcmQgZXJyb3JzIGlzIGRyYXduIGZyb20gdGhlICoqcG9zdGVyaW9yIGRpc3RyaWJ1dGlvbioqIG9mIHRoZSBwYXJhbWV0ZXJzLCB0eXBpY2FsbHkgYXNzdW1pbmcgYSBub24taW5mb3JtYXRpdmUgKGZsYXQpIHByaW9yIGRpc3RyaWJ1dGlvbi4gVGhlIGltcG9ydGFudCB0aGluZyB0byBub3RlIGlzIHRoYXQgdGhpcyBtZXRob2QgZGlyZWN0bHkgYWNjb3VudHMgZm9yIG91ciB1bmNlcnRhaW50eSBhc3NvY2lhdGVkIHdpdGggYm90aCB0aGUgc2FtcGxpbmcgdmFyaWFuY2Ugb2YgdGhlIGNvZWZmaWNpZW50cyB1c2VkIGluIHRoZSBpbXB1dGF0aW9uIG1vZGVsIGFzIHdlbGwgYXMgdGhlIHVuY2VydGFpbnR5IGRlcml2ZWQgZnJvbSB0aGUgbWlzc2luZ25lc3MgaXRzZWxmLgoKIyMgSW5mZXJlbmNlIGZvciBpbmRpdmlkdWFsIGNvZWZmaWNpZW50cwoKV2UgdXNlIHRoaXMgbWV0aG9kIHRvIHByb2R1Y2UgJGckIGNvbXBsZXRlIGRhdGFzZXRzLiBNSSBlc3RpbWF0ZXMgb2YgcG9wdWxhdGlvbiBwYXJhbWV0ZXJzIG9mIGludGVyZXN0IChzdWNoIGFzIGEgcmVncmVzc2lvbiBjb2VmZmljaWVudCkgYXJlIG9idGFpbmVkIGJ5IGF2ZXJhZ2luZyBvdmVyIHRoZSBpbXB1dGVkIGRhdGFzZXRzOgoKJCRcdGlsZGV7XGJldGF9X2ogXGVxdWl2IFxmcmFje1xzdW1fe2w9MX1eZyBCX2peeyhsKX19e2d9JCQKCj4gVGhpcyBhdmVyYWdpbmcgbWV0aG9kIGFwcGxpZXMgdG8gYW55IHR5cGUgb2YgcGFyYW1ldGVyIHRoYXQgZm9yIHRoZSBzZXBhcmF0ZSBlc3RpbWF0ZXMgaXMgYXBwcm94aW1hdGVseSBub3JtYWxseSBkaXN0cmlidXRlZC4gVGhpcyBhcHBsaWVzIHRvIE9MUyByZWdyZXNzaW9uIGVzdGltYXRlcywgR0xNIGNvZWZmaWNpZW50IGVzdGltYXRlcywgb3IgYnkgYW55IHBhcmFtZXRyaWMgbWV0aG9kIG9mIHJlZ3Jlc3Npb24gYW5hbHlzaXMuCgpTdGFuZGFyZCBlcnJvcnMgb2YgdGhlIGVzdGltYXRlZCBjb2VmZmljaWVudHMgYXJlIG9idGFpbmVkIGJ5IGNvbWJpbmluZyBpbmZvcm1hdGlvbiBhYm91dCB3aXRoaW4tIGFuZCBiZXR3ZWVuLWltcHV0YXRpb24gdmFyaWF0aW9uIGluIHRoZSBjb2VmZmljaWVudHM6CgokJFx0aWxkZXtcdGV4dHtTRX19KFx0aWxkZXtcYmV0YX1faikgPSBcc3FydHtWX2peeyhXKX0gKyBcZnJhY3tnICsgMX17Z30gVl9qXnsoQil9fSQkCgp3aGVyZSB0aGUgd2l0aGluLWltcHV0YXRpb24gY29tcG9uZW50IGlzOgoKJCRWX2peeyhXKX0gPSBcZnJhY3tcc3VtX3tsPTF9XmcgXHRleHR7U0V9XjIoQl9qXnsobCl9KX17Z30kJAoKYW5kIHRoZSBiZXR3ZWVuLWltcHV0YXRpb24gY29tcG9uZW50IGlzOgoKJCRWX2peeyhCKX0gPSBcZnJhY3tcc3VtX3tsPTF9XmcgKEJfal57KGwpfSAtIFx0aWxkZXtCfV9qKV4yfXtnLTF9JCQKCiRcdGV4dHtTRX1eMihCX2peeyhsKX0pJCBpcyB0aGUgc3RhbmRhcmQgZXJyb3Igb2YgJEJfaiQsIGNvbXB1dGVkIGluIHRoZSB1c3VhbCBtYW5uZXIgZm9yIHRoZSAkbCR0aCBpbXB1dGVkIGRhdGFzZXQuCgpJbmZlcmVuY2UgYmFzZWQgb24gJFx0aWxkZXtcdGV4dHtTRX19KFx0aWxkZXtcYmV0YX1faikkIGFuZCAkXHRpbGRle1x0ZXh0e1NFfX0oXHRpbGRle1xiZXRhfV9qKSQgZm9sbG93cyB0aGUgJHQkLWRpc3RyaWJ1dGlvbiB3aXRoIGRlZ3JlZXMgb2YgZnJlZWRvbToKCiQkZGZfaiA9IChnLTEpIFxsZWZ0ICggMSArIFxmcmFje2d9e2crMX0gXHRpbWVzIFxmcmFje1Zfal57KFcpfX17Vl9qXnsoQil9fSBccmlnaHQpXjIkJAoKIyMgUHJhY3RpY2FsIGNvbnNpZGVyYXRpb25zIGZvciBtdWx0aXBsZSBpbXB1dGF0aW9uCgpUaGUgTUkgbWV0aG9kIGlzIHR5cGljYWxseSBpbXBsZW1lbnRlZCBhc3N1bWluZyB0aGUgY29tcGxldGUgZGF0YSBmb2xsb3dzIGEgbXVsdGl2YXJpYXRlIG5vcm1hbCBkaXN0cmlidXRpb24uIFZpb2xhdGlvbiBvZiB0aGlzIGFzc3VtcHRpb24gaXNuJ3QgbmVjZXNzYXJpbHkgYSBkZWFsLWJyZWFrZXIgZm9yIHJlbHlpbmcgb24gTUkgZXN0aW1hdGVzLiBIb3dldmVyIE1JIGNhbiBvbmx5IHByZXNlcnZlIGZlYXR1cmVzIG9mIHRoZSBkYXRhc2V0IHJlcHJlc2VudGVkIGluIHRoZSBpbXB1dGF0aW9uIG1vZGVsLiBUaGVyZWZvcmUgeW91IG5lZWQgdG8gdGhpbmsgY2FyZWZ1bGx5IGFib3V0IHdoaWNoIGZlYXR1cmVzICh2YXJpYWJsZXMpIG5lZWQgdG8gYmUgcHJlc2VydmVkIHdoZW4gYnVpbGRpbmcgdGhlIGltcHV0YXRpb24gbW9kZWwgdG8gZW5zdXJlIHRob3NlIHBhcnRpY3VsYXIgZmVhdHVyZXMgd2lsbCBhcHBlYXIgaW4gdGhlIGZpbmFsIHN0YXRpc3RpY2FsIG1vZGVsLgoKKiAqKkluY2x1ZGUgdmFyaWFibGVzIGluIHRoZSBpbXB1dGF0aW9uIG1vZGVsIHRoYXQgbWFrZSB0aGUgYXNzdW1wdGlvbiBvZiBpZ25vcmFibGUgbWlzc2luZ25lc3MgcmVhc29uYWJsZS4qKiBSZW1lbWJlciB0aGF0IHRoZSBNSSBtZXRob2QgYXNzdW1lcyBkYXRhIGlzIE1BUi4gU28gZm9yIHRoaXMgdG8gd29yayBvbiBkYXRhIE1OQVIsIHdlIHdhbnQgdG8gYnVpbGQgYSBwcmVkaWN0aXZlIG1vZGVsIHRoYXQgZG9lcyBhIGdyZWF0IGpvYiBvZiBwcmVkaWN0aW5nIG1pc3NpbmcgdmFsdWVzLiBUeXBpY2FsbHkgdGhpcyBpbmNsdWRlcyB1c2luZyB2YXJpYWJsZXMgdGhhdCB3aWxsIGJlIGluIHRoZSBmaW5hbCBzdGF0aXN0aWNhbCBtb2RlbCBhcyB3ZWxsIGFzIHZhcmlhYmxlcyBpbiB0aGUgZGF0YXNldCBub3QgdXNlZCBpbiB0aGUgZmluYWwgc3RhdGlzdGljYWwgbW9kZWwsIHZhcmlhYmxlcyBzdHJvbmdseSBjb3JyZWxhdGVkIHdpdGggdGhlIHZhcmlhYmxlIHdpdGggbWlzc2luZ25lc3MgKGFzIG1lYXN1cmVkIGJ5IHRoZSBjb21wbGV0ZSBvYnNlcnZhdGlvbnMpLCBhbmQgZXZlbiB0aGUgcmVzcG9uc2UgdmFyaWFibGUgaXRzZWxmLiBUaGluayBvZiB0aGUgaW1wdXRhdGlvbiBtb2RlbCBhcyBhIHB1cmUgcHJlZGljdGlvbiBtb2RlbCAtIHlvdSBhcmUgbm90IGNvbmR1Y3RpbmcgaW5mZXJlbmNlIG9uIHRoZSBpbXB1dGF0aW9uIG1vZGVsIGl0c2VsZiwgc28gaXQgY2FuIGJlIGhpZ2hseSBjb21wbGV4LgoqICoqVHJhbnNmb3JtIHZhcmlhYmxlcyB0byBhcHByb3hpbWF0ZWx5IG5vcm1hbC4qKiBBZnRlciB0aGUgaW1wdXRlZCBkYXRhIGFyZSBvYnRhaW5lZCwgeW91IGNhbiB0cmFuc2Zvcm0gdGhlbSBiYWNrIHRvIHRoZWlyIG9yaWdpbmFsIHNjYWxlcyBwcmlvciB0byBhbmFseXppbmcgdGhlIGNvbXBsZXRlZCBkYXRhc2V0cy4KKiAqKkFkanVzdCB0aGUgaW1wdXRlZCBkYXRhIHRvIHJlc2VtYmxlIHRoZSBvcmlnaW5hbCBkYXRhLioqIFNvIGlmIHlvdSBoYXZlIGEgZGljaG90b21vdXMgdmFyaWFibGUgd2l0aCBpbXB1dGVkIHZhbHVlcyBvZiAkLjMkIG9yICQuNzg1JCwgcm91bmQgdGhlbSB0byAkMCQgYW5kICQxJC4KKiAqKk1ha2Ugc3VyZSB0aGUgaW1wdXRhdGlvbiBtb2RlbCBjYXB0dXJlcyByZWxldmFudCBmZWF0dXJlcyBvZiB0aGUgZGF0YS4qKiBBZ2FpbiwgY29uc2lkZXIgaG93IHRoZSBkYXRhIHdpbGwgZXZlbnR1YWxseSBiZSBhbmFseXplZC4gVGhlIG11bHRpdmFyaWF0ZSBub3JtYWwgZGlzdHJpYnV0aW9uIGVuc3VyZXMgdGhhdCByZWdyZXNzaW9ucyBvZiBvbmUgdmFyaWFibGUgb24gb3RoZXJzIGFyZSBsaW5lYXIgYW5kIGFkZGl0aXZlLiBJZiB5b3UgYXJlIGVzdGltYXRpbmcgYSAqKm5vbmxpbmVhcioqIHJlbGF0aW9uc2hpcCAoZWl0aGVyIHBvbHlub21pYWwgb3IgaW50ZXJhY3RpdmUpLCB0aGVuIHByb3ZpZGUgZm9yIHRoYXQgaW4gdGhlIGltcHV0YXRpb24gbW9kZWwgKGV4cGxpY2l0bHkgYWRkIHRoZSBwb2x5bm9taWFsIG9yIGludGVyYWN0aW9uIHRlcm0pLgoqICoqJGckIGRvZXNuJ3QgbmVlZCB0byBiZSBsYXJnZS4qKiBGb3IgbW9zdCBzaXR1YXRpb25zLCAkZz01JCBvciAkZz0xMCQgaXMgYWN0dWFsbHkgc3VpdGFibGUgZm9yIHN0YXRpc3RpY2FsIGluZmVyZW5jZS4KCiMgUmVncmVzc2lvbiBtb2RlbCBvZiBpbmZhbnQgbW9ydGFsaXR5IHdpdGggTUkKCmBgYHtyIGltcG9ydC11bn0KdW4gPC0gcmVhZF9kZWxpbSgiZGF0YS9Vbml0ZWROYXRpb25zLnR4dCIsIGRlbGltID0gIiAiKQpgYGAKCmBgYHtyIHBsb3QtaW5mYW50LWdkcH0KZ2dwbG90KHVuLCBhZXMoR0RQcGVyQ2FwaXRhLCBpbmZhbnRNb3J0YWxpdHkpKSArCiAgZ2VvbV9wb2ludCgpICsKICBnZW9tX3Ntb290aChzZSA9IEZBTFNFKSArCiAgc2NhbGVfeF9jb250aW51b3VzKGxhYmVscyA9IHNjYWxlczo6ZG9sbGFyKSArCiAgbGFicyh4ID0gIkdEUCBwZXIgY2FwaXRhIChpbiBVU0QpIiwKICAgICAgIHkgPSAiSW5mYW50IG1vcnRhbGl0eSByYXRlIChwZXIgMSwwMDApIikKYGBgCgpUaGUgYWJvdmUgZmlndXJlIHNob3dzIHRoZSByZWxhdGlvbnNoaXAgYmV0d2VlbiBHRFAgcGVyIGNhcGl0YSBhbmQgaW5mYW50IG1vcnRhbGl0eSBpbiBgciBucm93KGZpbHRlcih1biwgIWlzLm5hKEdEUHBlckNhcGl0YSksICFpcy5uYShpbmZhbnRNb3J0YWxpdHkpKSlgIGNvdW50cmllcywgcGFydCBvZiBhIGxhcmdlciBkYXRhc2V0IG9mIGByIG5yb3codW4pYCBjb3VudHJpZXMgY29tcGlsZWQgYnkgdGhlIFVuaXRlZCBOYXRpb25zLiBUaGUgYW1vdW50IG9mIG1pc3NpbmduZXNzIGluIHRoZSBmaWd1cmUgaXMgdGhlcmVmb3JlIHNtYWxsLCBhcHByb3hpbWF0ZWx5IGByIGZvcm1hdEMoKDEgLSBucm93KGZpbHRlcih1biwgIWlzLm5hKEdEUHBlckNhcGl0YSksICFpcy5uYShpbmZhbnRNb3J0YWxpdHkpKSkgLyBucm93KHVuKSkgKiAxMDAsIGRpZ2l0cyA9IDEpYCUgb2YgdGhlIGNhc2VzLgoKTGV0J3Mgbm93IGVzdGltYXRlIGEgbGluZWFyIHJlZ3Jlc3Npb24gbW9kZWwgb2YgaW5mYW50IG1vcnRhbGl0eSBub3Qgb25seSBvbiBHRFAgcGVyIGNhcGl0YSBidXQgYWxzbyB0aGUgcGVyY2VudGFnZSBvZiBtYXJyaWVkIHdvbWVuIHByYWN0aWNpbmcgY29udHJhY2VwdGlvbiBhbmQgdGhlIGF2ZXJhZ2UgbnVtYmVyIG9mIHllYXJzIG9mIGVkdWNhdGlvbiBmb3Igd29tZW4uIFRvIGxpbmVhcml6ZSB0aGUgbW9kZWwsIHdlIGxvZy10cmFuc2Zvcm0gYm90aCBpbmZhbnQgbW9ydGFsaXR5IGFuZCBHRFAuCgpgYGB7ciBsb2ctbG9nfQpnZ3Bsb3QodW4sIGFlcyhHRFBwZXJDYXBpdGEsIGluZmFudE1vcnRhbGl0eSkpICsKICBnZW9tX3BvaW50KCkgKwogIGdlb21fc21vb3RoKG1ldGhvZCA9ICJsbSIsIHNlID0gRkFMU0UpICsKICBzY2FsZV94X2xvZzEwKGxhYmVscyA9IHNjYWxlczo6ZG9sbGFyKSArCiAgc2NhbGVfeV9sb2cxMCgpICsKICBsYWJzKHggPSAiR0RQIHBlciBjYXBpdGEgKGluIFVTRCkiLAogICAgICAgeSA9ICJJbmZhbnQgbW9ydGFsaXR5IHJhdGUgKHBlciAxLDAwMCkiKQoKbW9ydGFsX21vZCA8LSBsbShsb2coaW5mYW50TW9ydGFsaXR5KSB+IGxvZyhHRFBwZXJDYXBpdGEpICsKICAgICAgICAgICAgICAgICAgIGNvbnRyYWNlcHRpb24gKyBlZHVjYXRpb25GZW1hbGUsCiAgICAgICAgICAgICAgICAgZGF0YSA9IHVuKQp0aWR5KG1vcnRhbF9tb2QpCmBgYAoKV2l0aCBsaXN0d2lzZSBkZWxldGlvbiwgd2UgYXJlIGxlZnQgd2l0aCBqdXN0IGByIG5yb3cobW9ydGFsX21vZCRtb2RlbClgIG9ic2VydmF0aW9ucy4gVGhlIG1pc3NpbmduZXNzIGZvciBlYWNoIHZhcmlhYmxlIGlzOgoKYGBge3IgbWlzcy1wYXR0ZXJufQp1biAlPiUKICBzZWxlY3QoaW5mYW50TW9ydGFsaXR5LCBHRFBwZXJDYXBpdGEsIGNvbnRyYWNlcHRpb24sIGVkdWNhdGlvbkZlbWFsZSkgJT4lCiAgc3VtbWFyaXplX2FsbChmdW5zKHN1bShpcy5uYSguKSkpKSAlPiUKICBrbml0cjo6a2FibGUoKQpgYGAKCiMjIGBBbWVsaWFgCgpbYEFtZWxpYWBdKGh0dHBzOi8vZ2tpbmcuaGFydmFyZC5lZHUvQW1lbGlhKSBpcyBhIHBhY2thZ2UgZm9yIFIgdGhhdCBwcm92aWRlcyBhIEJheWVzaWFuIEVNLWJhc2VkIGFsZ29yaXRobSBmb3IgbXVsdGlwbGUgaW1wdXRhdGlvbi5eW1RlY2huaWNhbCBkZXRhaWxzIG9mIHRoZSBhbGdvcml0aG0ncyBpbXBsZW1lbnRhdGlvbiBjYW4gYmUgcmVhZCBbaGVyZV0oaHR0cHM6Ly9na2luZy5oYXJ2YXJkLmVkdS9maWxlcy9na2luZy9maWxlcy9hbWVsaWFfanNzLnBkZikuXSBUbyBjcmVhdGUgbXVsdGlwbGUgaW1wdXRhdGlvbnMgaW4gQW1lbGlhLCB3ZSB1c2UgYGFtZWxpYSgpYDoKCmBgYHtyIHVuLWFtZWxpYX0KbGlicmFyeShBbWVsaWEpCnVuLm91dCA8LSBhbWVsaWEoYXMuZGF0YS5mcmFtZSh1biksIG0gPSA1LCBpZHZhcnMgPSBjKCJjb3VudHJ5IiwgInJlZ2lvbiIpKQpgYGAKCj4gSWYgeW91ciBkYXRhIGZyYW1lIGlzIGEgYHRpYmJsZWAsIHlvdSBuZWVkIHRvIHR1cm4gYmFjayBpbnRvIGEgcGxhaW4gZGF0YSBmcmFtZSB1c2luZyBgYXMuZGF0YS5mcmFtZSgpYCBpbiBvcmRlciB0byBzdWNjZXNzZnVsbHkgaW1wdXRlIHRoZSBkYXRhLgoKSGVyZSB3ZSBzcGVjaWZ5IGBjb3VudHJ5YCBhbmQgYHJlZ2lvbmAgYXJlIGlkIHZhcmlhYmxlcyAodGV4dCBzdHJpbmdzKSBhbmQgd2UgZG9uJ3Qgd2FudCB0byB1c2UgdGhlbSB0byBnZW5lcmF0ZSBpbXB1dGVkIHZhbHVlcy4gQnkgZGVmYXVsdCwgYGFtZWxpYSgpYCB1c2VzIGFsbCB0aGUgdmFyaWFibGVzIGluIHRoZWlyIHJhdyBmb3JtcyB0byBpbXB1dGUgbWlzc2luZyB2YWx1ZXMgZm9yIGVhY2ggdmFyaWFibGUuIENsZWFybHkgd2Ugc3RpbGwgd2FudCB0byB0dW5lIHRoaXMgYXBwcm9hY2gsIGJ1dCBmb3Igbm93IGxldCdzIHJ1biB3aXRoIGl0LiBUaGUgbGlzdCBvZiBpbXB1dGVkIGRhdGEgZnJhbWVzIGlzIHN0b3JlZCBpbiB0aGUgYGltcHV0YXRpb25zYCBlbGVtZW50OgoKYGBge3IgYW1lbGlhLXJlc3VsdH0KZ2xpbXBzZSh1bi5vdXQkaW1wdXRhdGlvbnMpCmBgYAoKRWFjaCBvZiB0aGVzZSBpbXB1dGVkIGRhdGFzZXRzIGlzIGEgY29tcGxldGUgZGF0YSBmcmFtZS4gU28gZm9yIGV4YW1wbGUsIHdlIGNvdWxkIHBsb3QgdGhlIHNhbWUgc2NhdHRlcnBsb3Qgb2YgR0RQIHZzLiBpbmZhbnQgbW9ydGFsaXR5IHdpdGggdGhlIGltcHV0ZWQgdmFsdWVzIGZvciB0aGUgYHIgbnJvdyhmaWx0ZXIodW4sIGlzLm5hKEdEUHBlckNhcGl0YSkgfCBpcy5uYShpbmZhbnRNb3J0YWxpdHkpKSlgIGNvdW50cmllcyB3aXRoIG1pc3NpbmcgdmFsdWVzLgoKYGBge3IgcGxvdC1pbXB1dH0KdW4ub3V0JGltcHV0YXRpb25zICU+JQogIG1hcChhcy5kYXRhLmZyYW1lKSAlPiUKICBiaW5kX3Jvd3MoLmlkID0gImltcHV0ZSIpICU+JQogIGdncGxvdChhZXMoR0RQcGVyQ2FwaXRhLCBpbmZhbnRNb3J0YWxpdHkpKSArCiAgZ2VvbV9wb2ludCgpICsKICBnZW9tX3Ntb290aChzZSA9IEZBTFNFKSArCiAgc2NhbGVfeF9jb250aW51b3VzKGxhYmVscyA9IHNjYWxlczo6ZG9sbGFyKSArCiAgZmFjZXRfZ3JpZChpbXB1dGUgfiAuKSArCiAgbGFicyh4ID0gIkdEUCBwZXIgY2FwaXRhIChpbiBVU0QpIiwKICAgICAgIHkgPSAiSW5mYW50IG1vcnRhbGl0eSByYXRlIChwZXIgMSwwMDApIikKYGBgCgpOb3RpY2UgdGhhdCBmb3Igc29tZSBvZiB0aGUgaW1wdXRlZCBkYXRhc2V0cywgdGhlIGltcHV0ZWQgdmFsdWVzIGFyZSBub25zZW5zaWNhbDsgZm9yIGluc3RhbmNlLCB5b3UgY2Fubm90IGhhdmUgYSBuZWdhdGl2ZSBHRFAgb3IgaW5mYW50IG1vcnRhbGl0eSByYXRlLiBCdXQgYWdhaW4sIGxldCdzIGp1c3QgcnVuIHdpdGggaXQuCgpXZSBjYW4gdXNlIGBwdXJycjo6bWFwKClgIHRvIGVzdGltYXRlIHRoZSBsaW5lYXIgbW9kZWwgZnJvbSBiZWZvcmUgb24gdGhlIG5ldyBpbXB1dGVkIGRhdGFzZXRzIGFuZCBleHRyYWN0IHRoZSBjb2VmZmljaWVudHMgYW5kIHN0YW5kYXJkIGVycm9ycyB3aXRoIGBicm9vbTo6dGlkeSgpYDoKCmBgYHtyIGFtZWxpYS1wdXJycn0KbW9kZWxzX2ltcCA8LSBkYXRhX2ZyYW1lKGRhdGEgPSB1bi5vdXQkaW1wdXRhdGlvbnMpICU+JQogIG11dGF0ZShtb2RlbCA9IG1hcChkYXRhLCB+IGxtKGxvZyhpbmZhbnRNb3J0YWxpdHkpIH4gbG9nKEdEUHBlckNhcGl0YSkgKwogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgY29udHJhY2VwdGlvbiArIGVkdWNhdGlvbkZlbWFsZSwKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBkYXRhID0gLngpKSwKICAgICAgICAgY29lZiA9IG1hcChtb2RlbCwgdGlkeSkpICU+JQogIHVubmVzdChjb2VmLCAuaWQgPSAiaWQiKQptb2RlbHNfaW1wCmBgYAoKVG8gY29uZHVjdCBpbmZlcmVuY2UsIHdlIG5lZWQgdG8gYXZlcmFnZSB0aGUgZXN0aW1hdGVzIG9mIHRoZSBjb2VmZmljaWVudHMgYW5kIHRoZSBzdGFuZGFyZCBlcnJvcnMuIGBtaS5tZWxkKClgIGZyb20gYEFtZWxpYWAgZG9lcyB0aGUgd29yayBmb3IgdXM6CgpgYGB7ciBtaS1tZWxkfQptaS5tZWxkLnBsdXMgPC0gZnVuY3Rpb24oZGZfdGlkeSl7CiAgIyB0cmFuc2Zvcm0gZGF0YSBpbnRvIGFwcHJvcHJpYXRlIG1hdHJpeCBzaGFwZQogIGNvZWYub3V0IDwtIGRmX3RpZHkgJT4lCiAgICBzZWxlY3QoaWQ6ZXN0aW1hdGUpICU+JQogICAgc3ByZWFkKHRlcm0sIGVzdGltYXRlKSAlPiUKICAgIHNlbGVjdCgtaWQpCiAgCiAgc2Uub3V0IDwtIGRmX3RpZHkgJT4lCiAgICBzZWxlY3QoaWQsIHRlcm0sIHN0ZC5lcnJvcikgJT4lCiAgICBzcHJlYWQodGVybSwgc3RkLmVycm9yKSAlPiUKICAgIHNlbGVjdCgtaWQpCiAgCiAgY29tYmluZWQucmVzdWx0cyA8LSBtaS5tZWxkKHEgPSBjb2VmLm91dCwgc2UgPSBzZS5vdXQpCiAgCiAgZGF0YV9mcmFtZSh0ZXJtID0gY29sbmFtZXMoY29tYmluZWQucmVzdWx0cyRxLm1pKSwKICAgICAgICAgICAgIGVzdGltYXRlLm1pID0gY29tYmluZWQucmVzdWx0cyRxLm1pWzEsIF0sCiAgICAgICAgICAgICBzdGQuZXJyb3IubWkgPSBjb21iaW5lZC5yZXN1bHRzJHNlLm1pWzEsIF0pCn0KCiMgY29tcGFyZSByZXN1bHRzCnRpZHkobW9ydGFsX21vZCkgJT4lCiAgbGVmdF9qb2luKG1pLm1lbGQucGx1cyhtb2RlbHNfaW1wKSkgJT4lCiAgc2VsZWN0KC1zdGF0aXN0aWMsIC1wLnZhbHVlKQpgYGAKCldlIHNlZSBzb21lIGRpZmZlcmVuY2VzIGluIG91ciBlc3RpbWF0ZWQgY29lZmZpY2llbnRzIGFuZCBzdGFuZGFyZCBlcnJvcnMuCgojIyMgTWlzc2luZ25lc3MgbWFwCgpgbWlzc21hcCgpYCBpcyBhIHVzZWZ1bCBmdW5jdGlvbiBpbiBBbWVsaWEgdGhhdCB2aXN1YWxpemVzIHRoZSBtaXNzaW5nbmVzcyBpbiB0aGUgZGF0YToKCmBgYHtyIG1pc3MtbWFwfQptaXNzbWFwKHVuLm91dCkKYGBgCgojIyMgVHJhbnNmb3JtaW5nIHZhcmlhYmxlcwoKTGV0J3MgdGhpbmsgbW9yZSBjYXJlZnVsbHkgYWJvdXQgd2hhdCB2YXJpYWJsZXMgdG8gaW5jbHVkZSBpbiB0aGUgaW1wdXRhdGlvbiBtb2RlbCBhbmQgaG93IHRvIHNwZWNpZnkgdGhlbS4gRmlyc3QsIHdoaWNoIHZhcmlhYmxlcyBhcmUgaGlnaGx5IGNvcnJlbGF0ZWQgd2l0aCBjb250cmFjZXB0aW9uIGFuZCBmZW1hbGUgZWR1Y2F0aW9uPwoKYGBge3IgaGVhdG1hcH0KR0dhbGx5OjpnZ3BhaXJzKHNlbGVjdF9pZih1biwgaXMubnVtZXJpYykpCmBgYAoKVmFyaWFibGVzIHN1Y2ggYXMgdG90YWwgZmVydGlsaXR5IHJhdGUgYW5kIHRoZSBpbGxpdGVyYWN5IHJhdGUgZm9yIHdvbWVuIGFyZSBzdHJvbmdseSBjb3JyZWxhdGVkIHdpdGggb3VyIG1pc3NpbmcgdmFyaWFibGVzLiBMZXQncyBub3cgbGltaXQgb3VyIGltcHV0YXRpb24gbW9kZWwgdG8ganVzdCB0aGUgZm91ciB2YXJpYWJsZXMgaW4gdGhlIG9yaWdpbmFsIHJlZ3Jlc3Npb24gbW9kZWwgcGx1cyB0aGUgdG90YWwgZmVydGlsaXR5IHJhdGUsIGV4cGVjdGF0aW9uIG9mIGxpZmUgZm9yIHdvbWVuLCBwZXJjZW50YWdlIG9mIHdvbWVuIGVuZ2FnZWQgaW4gZWNvbm9taWMgYWN0aXZpdHkgb3V0c2lkZSB0aGUgaG9tZSwgYW5kIHRoZSBpbGxpdGVyYWN5IHJhdGUgZm9yIHdvbWVuLgoKYGBge3Igc2VsZWN0LXVufQp1bl9saXRlIDwtIHVuICU+JQogIHNlbGVjdChpbmZhbnRNb3J0YWxpdHksIEdEUHBlckNhcGl0YSwgY29udHJhY2VwdGlvbiwgZWR1Y2F0aW9uRmVtYWxlLAogICAgICAgICB0ZnIsIGxpZmVGZW1hbGUsIGVjb25vbWljQWN0aXZpdHlGZW1hbGUsIGlsbGl0ZXJhY3lGZW1hbGUpCgpHR2FsbHk6OmdncGFpcnModW5fbGl0ZSkKYGBgCgpTZXZlcmFsIG9mIHRoZXNlIHZhcmlhYmxlcyBhcmUgY2xlYXJseSBub3Qgbm9ybWFsbHkgZGlzdHJpYnV0ZWQ7IHRyYW5zZm9ybWluZyB0aGVzZSB2YXJpYWJsZXMgd2lsbCBhbHNvIGhlbHAgbWFrZSB0aGUgZGF0YXNldCBtb3JlIG11bHRpdmFyaWF0ZSBub3JtYWwsIHNvIHdlIGNhbiB0cmFuc2Zvcm0gdGhlbSBiZWZvcmUgaW1wdXRhdGlvbi4gV2UgY291bGQgbWFudWFsbHkgdHJhbnNmb3JtIHRoZW0gdXNpbmcgYG11dGF0ZSgpYCwgYnV0IGBhbWVsaWEoKWAgaW5jbHVkZXMgb3B0aW9ucyBmb3IgdHJhbnNmb3JtaW5nIHZhcmlhYmxlcyBhcyBwYXJ0IG9mIHRoZSBpbXB1dGF0aW9uIHByb2Nlc3MuIFRoaXMgYWxsb3dzIHVzIHRvIHJldGFpbiB0aGUgb3JpZ2luYWwgdmFsdWVzIGZvciB0aGUgc3RhdGlzdGljYWwgbW9kZWxpbmcuCgpgYGB7ciBhbWVsaWEtdHJhbnNmb3JtfQp1bl9saXRlLm91dCA8LSBhbWVsaWEodW5fbGl0ZSwgbSA9IDUsCiAgICAgICAgICAgICAgICAgICAgICBsb2dzID0gYygiaW5mYW50TW9ydGFsaXR5IiwgIkdEUHBlckNhcGl0YSIpLAogICAgICAgICAgICAgICAgICAgICAgc3FydCA9IGMoInRmciIpKQpgYGAKCj4gYGFtZWxpYSgpYCBhbHNvIGluY2x1ZGVzIHN1cHBvcnQgZm9yIG5vbWluYWwgYW5kIG9yZGluYWwgdmFyaWFibGVzIGFuZCBjcm9zcy1zZWN0aW9uYWwgdGltZS1zZXJpZXMgZGF0YSwgYXMgd2VsbCBhcyBwdXJlIHRpbWUgc2VyaWVzIGRhdGEgYW5kIGFjY291bnRpbmcgZm9yIGxlYWRzIGFuZCBsYWdzLiBTZWUgdGhlIGhlbHAgZmlsZSBmb3IgbW9yZSBkZXRhaWxzLgoKV2hhdCBkb2VzIHRoZSByZXN1bHRpbmcgbW9kZWwgbG9vayBsaWtlIG5vdz8KCmBgYHtyIGFtZWxpYS10cmFucy1tb2R9Cm1vZGVsc190cmFuc19pbXAgPC0gZGF0YV9mcmFtZShkYXRhID0gdW5fbGl0ZS5vdXQkaW1wdXRhdGlvbnMpICU+JQogIG11dGF0ZShtb2RlbCA9IG1hcChkYXRhLCB+IGxtKGxvZyhpbmZhbnRNb3J0YWxpdHkpIH4gbG9nKEdEUHBlckNhcGl0YSkgKwogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgY29udHJhY2VwdGlvbiArIGVkdWNhdGlvbkZlbWFsZSwKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBkYXRhID0gLngpKSwKICAgICAgICAgY29lZiA9IG1hcChtb2RlbCwgdGlkeSkpICU+JQogIHVubmVzdChjb2VmLCAuaWQgPSAiaWQiKQptb2RlbHNfdHJhbnNfaW1wCgojIGNvbXBhcmUgcmVzdWx0cwp0aWR5KG1vcnRhbF9tb2QpICU+JQogIGxlZnRfam9pbihtaS5tZWxkLnBsdXMobW9kZWxzX3RyYW5zX2ltcCkpICU+JQogIHNlbGVjdCgtc3RhdGlzdGljLCAtcC52YWx1ZSkKCiMgY2hlYXRpbmcgb24gbXkgY29uZmlkZW5jZSBpbnRlcnZhbHMgZm9yIHRoaXMgcGxvdApiaW5kX3Jvd3Mob3JpZyA9IHRpZHkobW9ydGFsX21vZCksCiAgICAgICAgICBmdWxsX2ltcCA9IG1pLm1lbGQucGx1cyhtb2RlbHNfaW1wKSAlPiUKICAgICAgICAgICAgcmVuYW1lKGVzdGltYXRlID0gZXN0aW1hdGUubWksCiAgICAgICAgICAgICAgICAgICBzdGQuZXJyb3IgPSBzdGQuZXJyb3IubWkpLAogICAgICAgICAgdHJhbnNfaW1wID0gbWkubWVsZC5wbHVzKG1vZGVsc190cmFuc19pbXApICU+JQogICAgICAgICAgICByZW5hbWUoZXN0aW1hdGUgPSBlc3RpbWF0ZS5taSwKICAgICAgICAgICAgICAgICAgIHN0ZC5lcnJvciA9IHN0ZC5lcnJvci5taSksCiAgICAgICAgICAuaWQgPSAibWV0aG9kIikgJT4lCiAgbXV0YXRlKG1ldGhvZCA9IGZhY3RvcihtZXRob2QsIGxldmVscyA9IGMoIm9yaWciLCAiZnVsbF9pbXAiLCAidHJhbnNfaW1wIiksCiAgICAgICAgICAgICAgICAgICAgICAgICBsYWJlbHMgPSBjKCJMaXN0d2lzZSBkZWxldGlvbiIsICJGdWxsIGltcHV0YXRpb24iLAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAiVHJhbnNmb3JtZWQgaW1wdXRhdGlvbiIpKSwKICAgICAgICAgdGVybSA9IGZhY3Rvcih0ZXJtLCBsZXZlbHMgPSBjKCIoSW50ZXJjZXB0KSIsICJjb250cmFjZXB0aW9uIiwKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICJlZHVjYXRpb25GZW1hbGUiLCAibG9nKEdEUHBlckNhcGl0YSkiKSwKICAgICAgICAgICAgICAgICAgICAgICBsYWJlbHMgPSBjKCJJbnRlcmNlcHQiLCAiQ29udHJhY2VwdGlvbiIsICJGZW1hbGUgZWR1Y2F0aW9uIiwKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICJHRFAgcGVyIGNhcGl0YSAobG9nKSIpKSkgJT4lCiAgZ2dwbG90KGFlcyhmY3RfcmV2KHRlcm0pLCBlc3RpbWF0ZSwgY29sb3IgPSBmY3RfcmV2KG1ldGhvZCksCiAgICAgICAgICAgICB5bWluID0gZXN0aW1hdGUgLSAxLjk2ICogc3RkLmVycm9yLAogICAgICAgICAgICAgeW1heCA9IGVzdGltYXRlICsgMS45NiAqIHN0ZC5lcnJvcikpICsKICBnZW9tX2hsaW5lKHlpbnRlcmNlcHQgPSAwLCBsaW5ldHlwZSA9IDIpICsKICBnZW9tX3BvaW50cmFuZ2UocG9zaXRpb24gPSBwb3NpdGlvbl9kb2RnZSguNzUpKSArCiAgY29vcmRfZmxpcCgpICsKICBzY2FsZV9jb2xvcl9kaXNjcmV0ZShndWlkZSA9IGd1aWRlX2xlZ2VuZChyZXZlcnNlID0gVFJVRSkpICsKICBsYWJzKHRpdGxlID0gIkNvbXBhcmluZyByZWdyZXNzaW9uIHJlc3VsdHMiLAogICAgICAgeCA9IE5VTEwsCiAgICAgICB5ID0gIkVzdGltYXRlZCBwYXJhbWV0ZXIiLAogICAgICAgY29sb3IgPSBOVUxMKSArCiAgdGhlbWUobGVnZW5kLnBvc2l0aW9uID0gImJvdHRvbSIpCgpiaW5kX3Jvd3Mob3JpZyA9IHRpZHkobW9ydGFsX21vZCksCiAgICAgICAgICBmdWxsX2ltcCA9IG1pLm1lbGQucGx1cyhtb2RlbHNfaW1wKSAlPiUKICAgICAgICAgICAgcmVuYW1lKGVzdGltYXRlID0gZXN0aW1hdGUubWksCiAgICAgICAgICAgICAgICAgICBzdGQuZXJyb3IgPSBzdGQuZXJyb3IubWkpLAogICAgICAgICAgdHJhbnNfaW1wID0gbWkubWVsZC5wbHVzKG1vZGVsc190cmFuc19pbXApICU+JQogICAgICAgICAgICByZW5hbWUoZXN0aW1hdGUgPSBlc3RpbWF0ZS5taSwKICAgICAgICAgICAgICAgICAgIHN0ZC5lcnJvciA9IHN0ZC5lcnJvci5taSksCiAgICAgICAgICAuaWQgPSAibWV0aG9kIikgJT4lCiAgbXV0YXRlKG1ldGhvZCA9IGZhY3RvcihtZXRob2QsIGxldmVscyA9IGMoIm9yaWciLCAiZnVsbF9pbXAiLCAidHJhbnNfaW1wIiksCiAgICAgICAgICAgICAgICAgICAgICAgICBsYWJlbHMgPSBjKCJMaXN0d2lzZSBkZWxldGlvbiIsICJGdWxsIGltcHV0YXRpb24iLAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAiVHJhbnNmb3JtZWQgaW1wdXRhdGlvbiIpKSwKICAgICAgICAgdGVybSA9IGZhY3Rvcih0ZXJtLCBsZXZlbHMgPSBjKCIoSW50ZXJjZXB0KSIsICJjb250cmFjZXB0aW9uIiwKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICJlZHVjYXRpb25GZW1hbGUiLCAibG9nKEdEUHBlckNhcGl0YSkiKSwKICAgICAgICAgICAgICAgICAgICAgICBsYWJlbHMgPSBjKCJJbnRlcmNlcHQiLCAiQ29udHJhY2VwdGlvbiIsICJGZW1hbGUgZWR1Y2F0aW9uIiwKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICJHRFAgcGVyIGNhcGl0YSAobG9nKSIpKSkgJT4lCiAgZmlsdGVyKHRlcm0gIT0gIkludGVyY2VwdCIpICU+JQogIGdncGxvdChhZXMoZmN0X3Jldih0ZXJtKSwgZXN0aW1hdGUsIGNvbG9yID0gZmN0X3JldihtZXRob2QpLAogICAgICAgICAgICAgeW1pbiA9IGVzdGltYXRlIC0gMS45NiAqIHN0ZC5lcnJvciwKICAgICAgICAgICAgIHltYXggPSBlc3RpbWF0ZSArIDEuOTYgKiBzdGQuZXJyb3IpKSArCiAgZ2VvbV9obGluZSh5aW50ZXJjZXB0ID0gMCwgbGluZXR5cGUgPSAyKSArCiAgZ2VvbV9wb2ludHJhbmdlKHBvc2l0aW9uID0gcG9zaXRpb25fZG9kZ2UoLjc1KSkgKwogIGNvb3JkX2ZsaXAoKSArCiAgc2NhbGVfY29sb3JfZGlzY3JldGUoZ3VpZGUgPSBndWlkZV9sZWdlbmQocmV2ZXJzZSA9IFRSVUUpKSArCiAgbGFicyh0aXRsZSA9ICJDb21wYXJpbmcgcmVncmVzc2lvbiByZXN1bHRzIiwKICAgICAgIHN1YnRpdGxlID0gIk9taXR0aW5nIGludGVyY2VwdCBmcm9tIHBsb3QiLAogICAgICAgeCA9IE5VTEwsCiAgICAgICB5ID0gIkVzdGltYXRlZCBwYXJhbWV0ZXIiLAogICAgICAgY29sb3IgPSBOVUxMKSArCiAgdGhlbWUobGVnZW5kLnBvc2l0aW9uID0gImJvdHRvbSIpCmBgYAoKIyBNdWx0aXBsZSBpbXB1dGF0aW9uIG91dHNpZGUgb2YgR0xNcwoKKiBUcmVlLWJhc2VkIGluZmVyZW5jZSAtIG1pc3NpbmcgdmFsdWUgYmVjb21lcyBhIGZlYXR1cmUgb2YgdGhlIGRhdGEKCiMgTUkgaW4gUHl0aG9uCgoqIFtgc2tsZWFybi5wcmVwcm9jZXNzaW5nLkltcHV0ZXJgXShodHRwOi8vc2Npa2l0LWxlYXJuLm9yZy9zdGFibGUvbW9kdWxlcy9nZW5lcmF0ZWQvc2tsZWFybi5wcmVwcm9jZXNzaW5nLkltcHV0ZXIuaHRtbCkgLSBmb3IgYmFzaWMgaW1wdXRhdGlvbiB3aXRoIG1lYW4sIG1lZGlhbiwgb3IgbW9kYWwgdmFsdWVzCiogW011bHRpcGxlIEltcHV0YXRpb24gd2l0aCBDaGFpbmVkIEVxdWF0aW9uc10oaHR0cDovL3d3dy5zdGF0c21vZGVscy5vcmcvZGV2L2ltcHV0YXRpb24uaHRtbCkgLSBmb3IgYHN0YXRzbW9kZWxzYAogICAgKiBVc2VzIHRoZSBwcmVkaWN0aXZlIG1lYW4gbWF0Y2hpbmcgdGVjaG5pcXVlCiogW01JREFTIC0gTXVsdGlwbGUgSW1wdXRhdGlvbiB3aXRoIERlbm9pc2luZyBBdXRvZW5jb2RlcnNdKGh0dHBzOi8vZ2l0aHViLmNvbS9PcmFjZW4vTUlEQVMpIC0gZGVlcCBsZWFybmluZyBtZXRob2QgZm9yIG11bHRpcGxlIGltcHV0YXRpb24KCiMgQWNrbm93bGVkZ21lbnRzIHsudG9jLWlnbm9yZX0KCiogW0ZveCwgSm9obi4gKkFwcGxpZWQgUmVncmVzc2lvbiBBbmFseXNpcyBhbmQgR2VuZXJhbGl6ZWQgTGluZWFyIE1vZGVscyouIDNyZCBlZGl0aW9uLiAyMDE2Ll0oaHR0cDovL3NvY3NlcnYuc29jc2NpLm1jbWFzdGVyLmNhL2pmb3gvQm9va3MvQXBwbGllZC1SZWdyZXNzaW9uLTNFL2luZGV4Lmh0bWwpCiogRm9yIG1vcmUgaW5mb3JtYXRpb24gb24gYWx0ZXJuYXRpdmUgcGFja2FnZXMgaW4gUiBmb3IgbXVsdGlwbGUgaW1wdXRhdGlvbiwgc2VlIFt0aGlzIHR1dG9yaWFsIG9uIG11bHRpcGxlIGltcHV0YXRpb24gaW4gUl0oaHR0cDovL3Rob21hc2xlZXBlci5jb20vUmNvdXJzZS9UdXRvcmlhbHMvbWkuaHRtbCkuCgojIFNlc3Npb24gSW5mbyB7LnRvYy1pZ25vcmV9CgpgYGB7ciBjaGlsZD0nX3Nlc3Npb25pbmZvLlJtZCd9CmBgYAo=

This work is licensed under the CC BY-NC 4.0 Creative Commons License.